Move explorer_old to the rosapps.
[reactos.git] / rosapps / applications / explorer-old / taskbar / traynotify.cpp
diff --git a/rosapps/applications/explorer-old/taskbar/traynotify.cpp b/rosapps/applications/explorer-old/taskbar/traynotify.cpp
new file mode 100644 (file)
index 0000000..802dcbc
--- /dev/null
@@ -0,0 +1,1354 @@
+/*
+ * Copyright 2003, 2004, 2005 Martin Fuchs
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+ //
+ // Explorer clone
+ //
+ // traynotify.cpp
+ //
+ // Martin Fuchs, 22.08.2003
+ //
+
+
+#include <precomp.h>
+
+#include "traynotify.h"
+
+#include "../notifyhook/notifyhook.h"
+
+NotifyHook::NotifyHook()
+ :     WM_GETMODULEPATH(InstallNotifyHook())
+{
+}
+
+NotifyHook::~NotifyHook()
+{
+       DeinstallNotifyHook();
+}
+
+void NotifyHook::GetModulePath(HWND hwnd, HWND hwndCallback)
+{
+       PostMessage(hwnd, WM_GETMODULEPATH, (WPARAM)hwndCallback, 0);
+}
+
+bool NotifyHook::ModulePathCopyData(LPARAM lparam, HWND* phwnd, String& path)
+{
+       char buffer[MAX_PATH];
+
+       int l = GetWindowModulePathCopyData(lparam, phwnd, buffer, COUNTOF(buffer));
+
+       if (l) {
+               path.assign(buffer, l);
+               return true;
+       } else
+               return false;
+}
+
+
+NotifyIconIndex::NotifyIconIndex(NOTIFYICONDATA* pnid)
+{
+       _hWnd = pnid->hWnd;
+       _uID = pnid->uID;
+
+        // special handling for windows task manager
+       if ((int)_uID < 0)
+               _uID = 0;
+}
+
+NotifyIconIndex::NotifyIconIndex()
+{
+       _hWnd = 0;
+       _uID = 0;
+}
+
+
+NotifyInfo::NotifyInfo()
+{
+       _idx = -1;
+       _hIcon = 0;
+       _dwState = 0;
+       _uCallbackMessage = 0;
+       _version = 0;
+
+       _mode = NIM_AUTO;
+       _lastChange = GetTickCount();
+}
+
+
+ // WCHAR versions von NOTIFYICONDATA
+#define        NID_SIZE_W6      sizeof(NOTIFYICONDATAW)                                                                                // _WIN32_IE = 0x600
+#define        NID_SIZE_W5     (sizeof(NOTIFYICONDATAW)-sizeof(GUID))                                                  // _WIN32_IE = 0x500
+#define        NID_SIZE_W3     (sizeof(NOTIFYICONDATAW)-sizeof(GUID)-(128-64)*sizeof(WCHAR))   // _WIN32_IE < 0x500
+
+ // CHAR versions von NOTIFYICONDATA
+#define        NID_SIZE_A6      sizeof(NOTIFYICONDATAA)
+#define        NID_SIZE_A5     (sizeof(NOTIFYICONDATAA)-sizeof(GUID))
+#define        NID_SIZE_A3     (sizeof(NOTIFYICONDATAA)-sizeof(GUID)-(128-64)*sizeof(CHAR))
+
+bool NotifyInfo::modify(NOTIFYICONDATA* pnid)
+{
+       bool changes = false;
+
+       if (_hWnd!=pnid->hWnd || _uID!=pnid->uID) {
+               _hWnd = pnid->hWnd;
+               _uID = pnid->uID;
+
+               changes = true;
+       }
+
+       if (pnid->uFlags & NIF_MESSAGE) {
+               if (_uCallbackMessage != pnid->uCallbackMessage) {
+                       _uCallbackMessage = pnid->uCallbackMessage;
+                       changes = true;
+               }
+       }
+
+       if (pnid->uFlags & NIF_ICON) {
+                // Some applications destroy the icon immediatelly after completing the
+                // NIM_ADD/MODIFY message, so we have to make a copy of it.
+               if (_hIcon)
+                       DestroyIcon(_hIcon);
+
+               _hIcon = (HICON) CopyImage(pnid->hIcon, IMAGE_ICON, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0);
+
+               changes = true; ///@todo compare icon
+       }
+
+       if (pnid->uFlags & NIF_STATE) {
+               DWORD new_state = (_dwState&~pnid->dwStateMask) | (pnid->dwState&pnid->dwStateMask);
+
+               if (_dwState != new_state) {
+                       _dwState = new_state;
+                       changes = true;
+               }
+       }
+
+        // store tool tip text
+       if (pnid->uFlags & NIF_TIP) {
+               String new_text;
+
+               if (pnid->cbSize==NID_SIZE_W6 || pnid->cbSize==NID_SIZE_W5 || pnid->cbSize==NID_SIZE_W3) {
+                        // UNICODE version of NOTIFYICONDATA structure
+                       LPCWSTR txt = (LPCWSTR)pnid->szTip;
+                       int max_len = pnid->cbSize==NID_SIZE_W3? 64: 128;
+
+                        // get tooltip string length
+                       int l = 0;
+                       for(; l<max_len; ++l)
+                               if (!txt[l])
+                                       break;
+
+                       new_text.assign(txt, l);
+
+                       if (new_text != _tipText) {
+                               _tipText = new_text;
+                               changes = true;
+                       }
+               } else if (pnid->cbSize==NID_SIZE_A6 || pnid->cbSize==NID_SIZE_A5 || pnid->cbSize==NID_SIZE_A3) {
+                       LPCSTR txt = (LPCSTR)pnid->szTip;
+                       int max_len = pnid->cbSize==NID_SIZE_A3? 64: 128;
+
+                       int l = 0;
+                       for(int l=0; l<max_len; ++l)
+                               if (!txt[l])
+                                       break;
+
+                       new_text.assign(txt, l);
+
+                       if (new_text != _tipText) {
+                               _tipText = new_text;
+                               changes = true;
+                       }
+               }
+       }
+
+       TCHAR title[MAX_PATH];
+
+       DWORD pid;
+       GetWindowThreadProcessId(_hWnd, &pid);
+
+        // avoid to send WM_GETTEXT messages to the own process
+       if (pid != GetCurrentProcessId())
+               if (GetWindowText(_hWnd, title, COUNTOF(title))) {
+                       if (_windowTitle != title) {
+                               _windowTitle = title;
+                               changes = true;
+                       }
+               }
+
+       if (changes) {
+               create_name();
+               _lastChange = GetTickCount();
+       }
+
+       return changes;
+}
+
+
+NotifyArea::NotifyArea(HWND hwnd)
+ :     super(hwnd),
+       _tooltip(hwnd)
+{
+       _next_idx = 0;
+       _clock_width = 0;
+       _last_icon_count = 0;
+       _show_hidden = false;
+       _hide_inactive = true;
+       _show_button = true;
+}
+
+NotifyArea::~NotifyArea()
+{
+       KillTimer(_hwnd, 0);
+
+       write_config();
+}
+
+static bool get_hide_clock_from_registry()
+{
+       HKEY hkeyStuckRects = 0;
+       DWORD buffer[10];
+       DWORD len = sizeof(buffer);
+
+       bool hide_clock = false;
+
+        // check if the clock should be hidden
+       if (!RegOpenKey(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects2"), &hkeyStuckRects) &&
+               !RegQueryValueEx(hkeyStuckRects, TEXT("Settings"), 0, NULL, (LPBYTE)buffer, &len) &&
+               len==sizeof(buffer) && buffer[0]==sizeof(buffer))
+               hide_clock = buffer[2] & 0x08? true: false;
+
+       if (hkeyStuckRects)
+               RegCloseKey(hkeyStuckRects);
+
+       return hide_clock;
+}
+
+void NotifyArea::read_config()
+{
+       bool clock_visible = true;
+
+        // read notification icon settings from XML configuration
+       XMLPos cfg_pos = g_Globals.get_cfg();
+
+       if (!g_Globals._SHRestricted || !SHRestricted(REST_HIDECLOCK))
+       {
+               if (cfg_pos.go_down("desktopbar")) {
+                       clock_visible = XMLBoolRef(XMLPos(cfg_pos,"options"), "show-clock", !get_hide_clock_from_registry());
+                       cfg_pos.back();
+               }
+       }
+
+       if (cfg_pos.go_down("notify-icons")) {
+               XMLPos options(cfg_pos, "options");
+
+               _hide_inactive = XMLBool(options, "hide-inactive", true);       ///@todo read default setting from registry
+               _show_hidden = XMLBool(options, "show-hidden", false);  ///@todo read default setting from registry
+               _show_button = XMLBool(options, "show-button", true);
+
+               XMLChildrenFilter icons(cfg_pos, "icon");
+
+               for(XMLChildrenFilter::iterator it=icons.begin(); it!=icons.end(); ++it) {
+                       const XMLNode& node = **it;
+
+                       NotifyIconConfig cfg;
+
+                       cfg._name = node.get("name").c_str();
+                       cfg._tipText = node.get("text").c_str();
+                       cfg._windowTitle = node.get("window").c_str();
+                       cfg._modulePath = node.get("module").c_str();
+                       const string& mode = node.get("show");
+
+                       if (mode == "show")
+                               cfg._mode = NIM_SHOW;
+                       else if (mode == "hide")
+                               cfg._mode = NIM_HIDE;
+                       else //if (mode == "auto")
+                               cfg._mode = NIM_HIDE;
+
+                       _cfg.push_back(cfg);
+               }
+
+               cfg_pos.back();
+       }
+
+       show_clock(clock_visible);
+}
+
+void NotifyArea::write_config()
+{
+        // write notification icon settings to XML configuration file
+       XMLPos cfg_pos = g_Globals.get_cfg();
+
+       cfg_pos.smart_create("desktopbar");
+       XMLBoolRef boolRef(XMLPos(cfg_pos,"options"), "show-clock");
+    boolRef = _hwndClock!=0;
+       cfg_pos.back();
+
+       cfg_pos.smart_create("notify-icons");
+
+       XMLPos options(cfg_pos, "options");
+       XMLBoolRef(options, "hide-inactive") = _hide_inactive;
+       XMLBoolRef(options, "show-hidden") = _show_hidden;
+       XMLBoolRef(options, "show-button") = _show_button;
+
+       for(NotifyIconCfgList::iterator it=_cfg.begin(); it!=_cfg.end(); ++it) {
+               NotifyIconConfig& cfg = *it;
+
+                // search for the corresponding node using the original name
+               cfg_pos.smart_create("icon", "name", cfg._name);
+
+                // refresh unique name
+               cfg.create_name();
+
+               cfg_pos["name"] = cfg._name.c_str();
+               cfg_pos["text"] = cfg._tipText.c_str();
+               cfg_pos["window"] = cfg._windowTitle.c_str();
+               cfg_pos["module"] = cfg._modulePath.c_str();
+               cfg_pos["show"] = string_from_mode(cfg._mode).c_str();
+
+               cfg_pos.back();
+       }
+
+       cfg_pos.back(); // smart_create
+}
+
+void NotifyArea::show_clock(bool flag)
+{
+       bool vis = _hwndClock!=0;
+
+       if (vis != flag) {
+               if (flag) {
+                        // create clock window
+                       _hwndClock = ClockWindow::Create(_hwnd);
+
+                       if (_hwndClock) {
+                               ClientRect clock_size(_hwndClock);
+                               _clock_width = clock_size.right;
+                       }
+               } else {
+                       DestroyWindow(_hwndClock);
+                       _hwndClock = 0;
+                       _clock_width = 0;
+               }
+
+               SendMessage(GetParent(_hwnd), PM_RESIZE_CHILDREN, 0, 0);
+       }
+}
+
+LRESULT NotifyArea::Init(LPCREATESTRUCT pcs)
+{
+       if (super::Init(pcs))
+               return 1;
+
+       read_config();
+
+       SetTimer(_hwnd, 0, 1000, NULL);
+
+       return 0;
+}
+
+HWND NotifyArea::Create(HWND hwndParent)
+{
+       static BtnWindowClass wcTrayNotify(CLASSNAME_TRAYNOTIFY, CS_DBLCLKS);
+
+       ClientRect clnt(hwndParent);
+
+       return Window::Create(WINDOW_CREATOR(NotifyArea), WS_EX_STATICEDGE,
+                                                       wcTrayNotify, TITLE_TRAYNOTIFY, WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN,
+                                                       clnt.right-(NOTIFYAREA_WIDTH_DEF+1), 1, NOTIFYAREA_WIDTH_DEF, clnt.bottom-2, hwndParent);
+}
+
+LRESULT NotifyArea::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
+{
+       switch(nmsg) {
+         case WM_PAINT:
+               Paint();
+               break;
+
+         case WM_TIMER: {
+               Refresh();
+
+               ClockWindow* clock_window = GET_WINDOW(ClockWindow, _hwndClock);
+
+               if (clock_window)
+                       clock_window->TimerTick();
+               break;}
+
+         case PM_REFRESH:
+               Refresh(true);
+               break;
+
+         case WM_SIZE: {
+               int cx = LOWORD(lparam);
+               SetWindowPos(_hwndClock, 0, cx-_clock_width, 0, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
+               break;}
+
+         case PM_GET_WIDTH: {
+               int w = _sorted_icons.size()*NOTIFYICON_DIST + NOTIFYAREA_SPACE + _clock_width;
+               if (_show_button)
+                       w += NOTIFYICON_DIST;
+               return w;}
+
+         case PM_REFRESH_CONFIG:
+               read_config();
+               break;
+
+         case WM_CONTEXTMENU: {
+               Point pt(lparam);
+               POINTS p;
+               p.x = (SHORT) pt.x;
+               p.y = (SHORT) pt.y;
+               ScreenToClient(_hwnd, &pt);
+
+               if (IconHitTest(pt) == _sorted_icons.end()) { // display menu only when no icon clicked
+                       PopupMenu menu(IDM_NOTIFYAREA);
+                       SetMenuDefaultItem(menu, 0, MF_BYPOSITION);
+                       CheckMenuItem(menu, ID_SHOW_HIDDEN_ICONS, MF_BYCOMMAND|(_show_hidden?MF_CHECKED:MF_UNCHECKED));
+                       CheckMenuItem(menu, ID_SHOW_ICON_BUTTON, MF_BYCOMMAND|(_show_button?MF_CHECKED:MF_UNCHECKED));
+                       menu.TrackPopupMenu(_hwnd, p);
+               }
+               break;}
+
+         case WM_COPYDATA: {   // receive NotifyHook answers
+               String path;
+               HWND hwnd;
+
+               if (_hook.ModulePathCopyData(lparam, &hwnd, path))
+                       _window_modules[hwnd] = path;
+               break;}
+
+         default:
+               if (nmsg>=WM_MOUSEFIRST && nmsg<=WM_MOUSELAST) {
+                        // close startup menu and other popup menus
+                        // This functionality is missing in MS Windows.
+                       if (nmsg==WM_LBUTTONDOWN || nmsg==WM_MBUTTONDOWN || nmsg==WM_RBUTTONDOWN
+#ifdef WM_XBUTTONDOWN
+                               || nmsg==WM_XBUTTONDOWN
+#endif
+                               )
+
+                               CancelModes();
+
+                       Point pt(lparam);
+                       NotifyIconSet::const_iterator found = IconHitTest(pt);
+
+                       if (found != _sorted_icons.end()) {
+                               const NotifyInfo& entry = const_cast<NotifyInfo&>(*found);      // Why does GCC 3.3 need this additional const_cast ?!
+
+                                // set activation time stamp
+                               if (nmsg == WM_LBUTTONDOWN ||   // Some programs need PostMessage() instead of SendMessage().
+                                       nmsg == WM_MBUTTONDOWN ||       // So call SendMessage() only for BUTTONUP and BLCLK messages
+#ifdef WM_XBUTTONDOWN
+                                       nmsg == WM_XBUTTONDOWN ||
+#endif
+                                       nmsg == WM_RBUTTONDOWN) {
+                                       _icon_map[entry]._lastChange = GetTickCount();
+                               }
+
+                                // Notify the message if the owner is still alive
+                               if (IsWindow(entry._hWnd)) {
+                                       if (nmsg == WM_MOUSEMOVE ||             // avoid to call blocking SendMessage() for merely moving the mouse over icons
+                                               nmsg == WM_LBUTTONDOWN ||       // Some programs need PostMessage() instead of SendMessage().
+                                               nmsg == WM_MBUTTONDOWN ||       // So call SendMessage() only for BUTTONUP and BLCLK messages
+#ifdef WM_XBUTTONDOWN
+                                               nmsg == WM_XBUTTONDOWN ||
+#endif
+                                               nmsg == WM_RBUTTONDOWN)
+                                               PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
+                                       else {
+                                                // allow SetForegroundWindow() in client process
+                                               DWORD pid;
+
+                                               if (GetWindowThreadProcessId(entry._hWnd, &pid)) {
+                                                        // bind dynamically to AllowSetForegroundWindow() to be compatible to WIN98
+                                                       static DynamicFct<BOOL(WINAPI*)(DWORD)> AllowSetForegroundWindow(TEXT("USER32"), "AllowSetForegroundWindow");
+
+                                                       if (AllowSetForegroundWindow)
+                                                               (*AllowSetForegroundWindow)(pid);
+                                               }
+
+                                                // use PostMessage() for notifcation icons of Shell Service Objects in the own process
+                                               if (pid == GetCurrentProcessId())
+                                                       PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
+                                               else
+                                                       SendMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
+                                       }
+                               }
+                               else if (_icon_map.erase(entry))        // delete icons without valid owner window
+                                       UpdateIcons();
+                       } else
+                                // handle clicks on notification area button "show hidden icons"
+                               if (_show_button)
+                                       if (nmsg == WM_LBUTTONDOWN)
+                                               if (pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
+                                                       pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
+                                                       PostMessage(_hwnd, WM_COMMAND, MAKEWPARAM(ID_SHOW_HIDDEN_ICONS,0), 0);
+               }
+
+               return super::WndProc(nmsg, wparam, lparam);
+       }
+
+       return 0;
+}
+
+int NotifyArea::Command(int id, int code)
+{
+       switch(id) {
+         case ID_SHOW_HIDDEN_ICONS:
+               _show_hidden = !_show_hidden;
+               UpdateIcons();
+               break;
+
+         case ID_SHOW_ICON_BUTTON:
+               _show_button = !_show_button;
+               UpdateIcons();
+               break;
+
+         case ID_CONFIG_NOTIFYAREA:
+               Dialog::DoModal(IDD_NOTIFYAREA, WINDOW_CREATOR(TrayNotifyDlg), GetParent(_hwnd));
+               break;
+
+         case ID_CONFIG_TIME:
+               launch_cpanel(_hwnd, TEXT("timedate.cpl"));
+               break;
+
+         default:
+               SendParent(WM_COMMAND, MAKELONG(id,code), 0);
+       }
+
+       return 0;
+}
+
+int NotifyArea::Notify(int id, NMHDR* pnmh)
+{
+       if (pnmh->code == TTN_GETDISPINFO) {
+               LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
+
+               Point pt(GetMessagePos());
+               ScreenToClient(_hwnd, &pt);
+
+               if (_show_button &&
+                       pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
+                       pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
+               {
+                       static ResString sShowIcons(IDS_SHOW_HIDDEN_ICONS);
+                       static ResString sHideIcons(IDS_HIDE_ICONS);
+
+                       pdi->lpszText = (_show_hidden? sHideIcons: sShowIcons).str();
+               } else {
+                       NotifyIconSet::iterator found = IconHitTest(pt);
+
+                       if (found != _sorted_icons.end()) {
+                               NotifyInfo& entry = const_cast<NotifyInfo&>(*found);    // Why does GCC 3.3 need this additional const_cast ?!
+
+                                // enable multiline tooltips (break at CR/LF and for very long one-line strings)
+                               SendMessage(pnmh->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 400);
+
+                               pdi->lpszText = entry._tipText.str();
+                       }
+               }
+       }
+
+       return 0;
+}
+
+void NotifyArea::CancelModes()
+{
+       PostMessage(HWND_BROADCAST, WM_CANCELMODE, 0, 0);
+
+       for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it)
+               PostMessage(it->_hWnd, WM_CANCELMODE, 0, 0);
+}
+
+LRESULT NotifyArea::ProcessTrayNotification(int notify_code, NOTIFYICONDATA* pnid)
+{
+       switch(notify_code) {
+         case NIM_ADD:
+         case NIM_MODIFY:
+               if ((int)pnid->uID >= 0) {      ///@todo This is a fix for Windows Task Manager.
+                       NotifyInfo& entry = _icon_map[pnid];
+
+                        // a new entry?
+                       if (entry._idx == -1)
+                               entry._idx = ++_next_idx;
+               /* equivalent code using iterator::find();
+                       NotifyIconMap::iterator found = _icon_map.find(pnid);
+                       NotifyInfo* pentry;
+                        // a new entry?
+                       if (found == _icon_map.end()) {
+                               pentry = &_icon_map[pnid];
+                               pentry->_idx = ++_next_idx;
+                       } else {
+                               pentry = &found->second;
+                       }
+                       NotifyInfo& entry = *pentry;
+               */
+                       bool changes = entry.modify(pnid);
+
+                       if (DetermineHideState(entry) && entry._mode==NIM_HIDE) {
+                               entry._dwState |= NIS_HIDDEN;
+                               changes = true;
+                       }
+
+                       if (changes)
+                               UpdateIcons();  ///@todo call only if really changes occurred
+
+                       return TRUE;
+               }
+               break;
+
+         case NIM_DELETE: {
+               NotifyIconMap::iterator found = _icon_map.find(pnid);
+
+               if (found != _icon_map.end()) {
+                       if (found->second._hIcon)
+                               DestroyIcon(found->second._hIcon);
+                       _icon_map.erase(found);
+                       UpdateIcons();
+                       return TRUE;
+               }
+               break;}
+
+         case NIM_SETFOCUS:
+               SetForegroundWindow(_hwnd);
+               return TRUE;
+
+         case NIM_SETVERSION:
+               NotifyIconMap::iterator found = _icon_map.find(pnid);
+
+               if (found != _icon_map.end()) {
+                       found->second._version = pnid->UNION_MEMBER(uVersion);
+                       return TRUE;
+               } else
+                       return FALSE;
+       }
+
+       return FALSE;
+}
+
+void NotifyArea::UpdateIcons()
+{
+       _sorted_icons.clear();
+
+        // sort icon infos by display index
+       for(NotifyIconMap::const_iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
+               const NotifyInfo& entry = it->second;
+
+               if (_show_hidden || !(entry._dwState & NIS_HIDDEN))
+                       _sorted_icons.insert(entry);
+       }
+
+        // sync tooltip areas to current icon number
+       if (_sorted_icons.size() != _last_icon_count) {
+               RECT rect = {NOTIFYICON_X, NOTIFYICON_Y, NOTIFYICON_X+NOTIFYICON_SIZE, NOTIFYICON_Y+NOTIFYICON_SIZE};
+
+               size_t tt_idx = 0;
+
+               if (_show_button) {
+                       _tooltip.add(_hwnd, tt_idx++, rect);
+
+                       rect.left += NOTIFYICON_DIST;
+                       rect.right += NOTIFYICON_DIST;
+               }
+
+               size_t icon_cnt = _sorted_icons.size();
+               while(tt_idx < icon_cnt) {
+                       _tooltip.add(_hwnd, tt_idx++, rect);
+
+                       rect.left += NOTIFYICON_DIST;
+                       rect.right += NOTIFYICON_DIST;
+               }
+
+               while(tt_idx < _last_icon_count)
+                       _tooltip.remove(_hwnd, tt_idx++);
+
+               _last_icon_count = _sorted_icons.size();
+       }
+
+       SendMessage(GetParent(_hwnd), PM_RESIZE_CHILDREN, 0, 0);
+
+       InvalidateRect(_hwnd, NULL, FALSE);     // refresh icon display
+       UpdateWindow(_hwnd);
+}
+
+#ifndef _NO_ALPHABLEND
+#ifdef _MSC_VER
+#pragma comment(lib, "msimg32")        // for AlphaBlend()
+#endif
+#endif
+
+void NotifyArea::Paint()
+{
+       BufferedPaintCanvas canvas(_hwnd);
+
+        // first fill with the background color
+       FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
+
+        // draw icons
+       int x = NOTIFYICON_X;
+       int y = NOTIFYICON_Y;
+
+       if (_show_button) {
+               static SmallIcon leftArrowIcon(IDI_NOTIFY_L);
+               static SmallIcon rightArrowIcon(IDI_NOTIFY_R);
+
+               DrawIconEx(canvas, x, y, _show_hidden?rightArrowIcon:leftArrowIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
+               x += NOTIFYICON_DIST;
+       }
+
+#ifndef _NO_ALPHABLEND
+       MemCanvas mem_dc;
+       SelectedBitmap bmp(mem_dc, CreateCompatibleBitmap(canvas, NOTIFYICON_SIZE, NOTIFYICON_SIZE));
+       RECT rect = {0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE};
+       BLENDFUNCTION blend = {AC_SRC_OVER, 0, 128, 0}; // 50 % visible
+#endif
+
+       for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
+#ifndef _NO_ALPHABLEND
+               if (it->_dwState & NIS_HIDDEN) {
+                       FillRect(mem_dc, &rect, GetSysColorBrush(COLOR_BTNFACE));
+                       DrawIconEx(mem_dc, 0, 0, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
+                       AlphaBlend(canvas, x, y, NOTIFYICON_SIZE, NOTIFYICON_SIZE, mem_dc, 0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE, blend);
+               } else
+#endif
+                       DrawIconEx(canvas, x, y, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
+
+               x += NOTIFYICON_DIST;
+       }
+}
+
+void NotifyArea::Refresh(bool update)
+{
+        // Look for task icons without valid owner window.
+        // This is an extended feature missing in MS Windows.
+       for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
+               const NotifyInfo& entry = *it;
+
+               if (!IsWindow(entry._hWnd))
+                       if (_icon_map.erase(entry))     // delete icons without valid owner window
+                               ++update;
+       }
+
+       DWORD now = GetTickCount();
+
+        // handle icon hiding
+       for(NotifyIconMap::iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
+               NotifyInfo& entry = it->second;
+
+               DetermineHideState(entry);
+
+               switch(entry._mode) {
+                 case NIM_HIDE:
+                       if (!(entry._dwState & NIS_HIDDEN)) {
+                               entry._dwState |= NIS_HIDDEN;
+                               ++update;
+                       }
+                       break;
+
+                 case NIM_SHOW:
+                       if (entry._dwState&NIS_HIDDEN) {
+                               entry._dwState &= ~NIS_HIDDEN;
+                               ++update;
+                       }
+                       break;
+
+                 case NIM_AUTO:
+                        // automatically hide icons after long periods of inactivity
+                       if (_hide_inactive)
+                               if (!(entry._dwState & NIS_HIDDEN))
+                                       if (now-entry._lastChange > ICON_AUTOHIDE_SECONDS*1000) {
+                                               entry._dwState |= NIS_HIDDEN;
+                                               ++update;
+                                       }
+                       break;
+               }
+       }
+
+       if (update)
+               UpdateIcons();
+}
+
+ /// search for a icon at a given client coordinate position
+NotifyIconSet::iterator NotifyArea::IconHitTest(const POINT& pos)
+{
+       if (pos.y<NOTIFYICON_Y || pos.y>=NOTIFYICON_Y+NOTIFYICON_SIZE)
+               return _sorted_icons.end();
+
+       NotifyIconSet::iterator it = _sorted_icons.begin();
+
+       int x = NOTIFYICON_X;
+
+       if (_show_button)
+               x += NOTIFYICON_DIST;
+
+       for(; it!=_sorted_icons.end(); ++it) {
+               //NotifyInfo& entry = const_cast<NotifyInfo&>(*it);     // Why does GCC 3.3 need this additional const_cast ?!
+
+               if (pos.x>=x && pos.x<x+NOTIFYICON_SIZE)
+                       break;
+
+               x += NOTIFYICON_DIST;
+       }
+
+       return it;
+}
+
+
+void NotifyIconConfig::create_name()
+{
+       _name = FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText.c_str(), _windowTitle.c_str(), _modulePath.c_str());
+}
+
+
+bool NotifyIconConfig::match(const NotifyIconConfig& props) const
+{
+       if (!_tipText.empty() && !props._tipText.empty())
+               if (props._tipText == _tipText)
+                       return true;
+
+       if (!_windowTitle.empty() && !props._windowTitle.empty())
+               if (_tcsstr(props._windowTitle, _windowTitle))
+                       return true;
+
+       if (!_modulePath.empty() && !props._modulePath.empty())
+               if (!_tcsicmp(props._modulePath, _modulePath))
+                       return true;
+
+       return false;
+}
+
+bool NotifyArea::DetermineHideState(NotifyInfo& entry)
+{
+       if (entry._modulePath.empty()) {
+               const String& modulePath = _window_modules[entry._hWnd];
+
+                // request module path for new windows (We will get an asynchronous answer by a WM_COPYDATA message.)
+               if (!modulePath.empty())
+                       entry._modulePath = modulePath;
+               else
+                       _hook.GetModulePath(entry._hWnd, _hwnd);
+       }
+
+       for(NotifyIconCfgList::const_iterator it=_cfg.begin(); it!=_cfg.end(); ++it) {
+               const NotifyIconConfig& cfg = *it;
+
+               if (cfg.match(entry)) {
+                       entry._mode = cfg._mode;
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
+
+String string_from_mode(NOTIFYICONMODE mode)
+{
+       switch(mode) {
+         case NIM_SHOW:
+               return ResString(IDS_NOTIFY_SHOW);
+
+         case NIM_HIDE:
+               return ResString(IDS_NOTIFY_HIDE);
+
+         default:      //case NIM_AUTO
+               return ResString(IDS_NOTIFY_AUTOHIDE);
+       }
+}
+
+
+TrayNotifyDlg::TrayNotifyDlg(HWND hwnd)
+ :     super(hwnd),
+       _tree_ctrl(GetDlgItem(hwnd, IDC_NOTIFY_ICONS)),
+       _himl(ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR24, 3, 0)),
+       _pNotifyArea(static_cast<NotifyArea*>(Window::get_window((HWND)SendMessage(g_Globals._hwndDesktopBar, PM_GET_NOTIFYAREA, 0, 0))))
+{
+       _selectedItem = 0;
+
+       if (_pNotifyArea) {
+                // save original icon states and configuration data
+               for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it)
+                       _icon_states_org[it->first] = IconStatePair(it->second._mode, it->second._dwState);
+
+               _cfg_org = _pNotifyArea->_cfg;
+               _show_hidden_org = _pNotifyArea->_show_hidden;
+       }
+
+       SetWindowIcon(hwnd, IDI_REACTOS);
+
+       _haccel = LoadAccelerators(g_Globals._hInstance, MAKEINTRESOURCE(IDA_TRAYNOTIFY));
+
+       {
+       WindowCanvas canvas(_hwnd);
+       HBRUSH hbkgnd = GetStockBrush(WHITE_BRUSH);
+
+       ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT), hbkgnd, canvas);
+       ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_TRANS), hbkgnd, canvas);
+       ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_RED), hbkgnd, canvas);
+       }
+
+       (void)TreeView_SetImageList(_tree_ctrl, _himl, TVSIL_NORMAL);
+
+       _resize_mgr.Add(IDC_NOTIFY_ICONS,       RESIZE);
+       _resize_mgr.Add(IDC_LABEL1,                     MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_TOOLTIP,     RESIZE_X|MOVE_Y);
+       _resize_mgr.Add(IDC_LABEL2,                     MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_TITLE,       RESIZE_X|MOVE_Y);
+       _resize_mgr.Add(IDC_LABEL3,                     MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_MODULE,      RESIZE_X|MOVE_Y);
+
+       _resize_mgr.Add(IDC_LABEL4,                     MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_SHOW,        MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_HIDE,        MOVE_Y);
+       _resize_mgr.Add(IDC_NOTIFY_AUTOHIDE,MOVE_Y);
+
+       _resize_mgr.Add(IDC_PICTURE,            MOVE);
+       _resize_mgr.Add(ID_SHOW_HIDDEN_ICONS,MOVE_Y);
+
+       _resize_mgr.Add(IDC_LABEL6,                     MOVE_Y);
+       _resize_mgr.Add(IDC_LAST_CHANGE,        MOVE_Y);
+
+       _resize_mgr.Add(IDOK,                           MOVE);
+       _resize_mgr.Add(IDCANCEL,                       MOVE);
+
+       _resize_mgr.Resize(+150, +200);
+
+       Refresh();
+
+       SetTimer(_hwnd, 0, 3000, NULL);
+       register_pretranslate(hwnd);
+}
+
+TrayNotifyDlg::~TrayNotifyDlg()
+{
+       KillTimer(_hwnd, 0);
+       unregister_pretranslate(_hwnd);
+       ImageList_Destroy(_himl);
+}
+
+void TrayNotifyDlg::Refresh()
+{
+       ///@todo refresh incrementally
+
+       HiddenWindow hide(_tree_ctrl);
+
+       TreeView_DeleteAllItems(_tree_ctrl);
+
+       TV_INSERTSTRUCT tvi;
+
+       tvi.hParent = 0;
+       tvi.hInsertAfter = TVI_LAST;
+
+       TV_ITEM& tv = tvi.item;
+       tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
+
+       ResString str_cur(IDS_ITEMS_CUR);
+       tv.pszText = str_cur.str();
+       tv.iSelectedImage = tv.iImage = 0;      // IDI_DOT
+       _hitemCurrent = TreeView_InsertItem(_tree_ctrl, &tvi);
+
+       ResString str_conf(IDS_ITEMS_CONFIGURED);
+       tv.pszText = str_conf.str();
+       tv.iSelectedImage = tv.iImage = 2;      // IDI_DOT_RED
+       _hitemConfig = TreeView_InsertItem(_tree_ctrl, &tvi);
+
+       tvi.hParent = _hitemCurrent;
+
+       ResString str_visible(IDS_ITEMS_VISIBLE);
+       tv.pszText = str_visible.str();
+       tv.iSelectedImage = tv.iImage = 0;      // IDI_DOT
+       _hitemCurrent_visible = TreeView_InsertItem(_tree_ctrl, &tvi);
+
+       ResString str_hidden(IDS_ITEMS_HIDDEN);
+       tv.pszText = str_hidden.str();
+       tv.iSelectedImage = tv.iImage = 1;      // IDI_DOT_TRANS
+       _hitemCurrent_hidden = TreeView_InsertItem(_tree_ctrl, &tvi);
+
+       if (_pNotifyArea) {
+               _info.clear();
+
+               tv.mask |= TVIF_PARAM;
+
+               WindowCanvas canvas(_hwnd);
+
+                // insert current (visible and hidden) items
+               for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it) {
+                       const NotifyInfo& entry = it->second;
+
+                       InsertItem(entry._dwState&NIS_HIDDEN? _hitemCurrent_hidden: _hitemCurrent_visible, TVI_LAST, entry, canvas);
+               }
+
+                // insert configured items in tree view
+               const NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
+               for(NotifyIconCfgList::const_iterator it=cfg.begin(); it!=cfg.end(); ++it) {
+                       const NotifyIconConfig& cfg_entry = *it;
+
+                       HICON hicon = 0;
+
+                       if (!cfg_entry._modulePath.empty()) {
+                               if ((int)ExtractIconEx(cfg_entry._modulePath, 0, NULL, &hicon, 1) <= 0)
+                                       hicon = 0;
+
+                               if (!hicon) {
+                                       SHFILEINFO sfi;
+
+                                       if (SHGetFileInfo(cfg_entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
+                                               hicon = sfi.hIcon;
+                               }
+                       }
+
+                       InsertItem(_hitemConfig, TVI_SORT, cfg_entry, canvas, hicon, cfg_entry._mode);
+
+                       if (hicon)
+                               DestroyIcon(hicon);
+               }
+
+               CheckDlgButton(_hwnd, ID_SHOW_HIDDEN_ICONS, _pNotifyArea->_show_hidden? BST_CHECKED: BST_UNCHECKED);
+       }
+
+       TreeView_Expand(_tree_ctrl, _hitemCurrent_visible, TVE_EXPAND);
+       TreeView_Expand(_tree_ctrl, _hitemCurrent_hidden, TVE_EXPAND);
+       TreeView_Expand(_tree_ctrl, _hitemCurrent, TVE_EXPAND);
+       TreeView_Expand(_tree_ctrl, _hitemConfig, TVE_EXPAND);
+
+       TreeView_EnsureVisible(_tree_ctrl, _hitemCurrent_visible);
+}
+
+void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyInfo& entry, HDC hdc)
+{
+       InsertItem(hparent, after, entry, hdc, entry._hIcon, entry._mode);
+}
+
+void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyIconDlgInfo& entry,
+                                                               HDC hdc, HICON hicon, NOTIFYICONMODE mode)
+{
+       int idx = _info.size() + 1;
+       _info[idx] = entry;
+
+       String mode_str = string_from_mode(mode);
+
+       switch(mode) {
+         case NIM_SHOW:        mode_str = ResString(IDS_NOTIFY_SHOW);          break;
+         case NIM_HIDE:        mode_str = ResString(IDS_NOTIFY_HIDE);          break;
+         case NIM_AUTO:        mode_str = ResString(IDS_NOTIFY_AUTOHIDE);
+       }
+
+       FmtString txt(TEXT("%s  -  %s  [%s]"), entry._tipText.c_str(), entry._windowTitle.c_str(), mode_str.c_str());
+
+       TV_INSERTSTRUCT tvi;
+
+       tvi.hParent = hparent;
+       tvi.hInsertAfter = after;
+
+       TV_ITEM& tv = tvi.item;
+       tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
+
+       tv.lParam = (LPARAM)idx;
+       tv.pszText = txt.str();
+       tv.iSelectedImage = tv.iImage = ImageList_AddAlphaIcon(_himl, hicon, GetStockBrush(WHITE_BRUSH), hdc);
+       (void)TreeView_InsertItem(_tree_ctrl, &tvi);
+}
+
+LRESULT TrayNotifyDlg::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
+{
+       switch(nmsg) {
+         case PM_TRANSLATE_MSG: {
+               MSG* pmsg = (MSG*) lparam;
+
+               if (TranslateAccelerator(_hwnd, _haccel, pmsg))
+                       return TRUE;
+
+               return FALSE;}
+
+         case WM_TIMER:
+               Refresh();
+               break;
+
+         default:
+               return super::WndProc(nmsg, wparam, lparam);
+       }
+
+       return 0;
+}
+
+int TrayNotifyDlg::Command(int id, int code)
+{
+       if (code == BN_CLICKED) {
+               switch(id) {
+                 case ID_REFRESH:
+                       Refresh();
+                       break;
+
+                 case IDC_NOTIFY_SHOW:
+                       SetIconMode(NIM_SHOW);
+                       break;
+
+                 case IDC_NOTIFY_HIDE:
+                       SetIconMode(NIM_HIDE);
+                       break;
+
+                 case IDC_NOTIFY_AUTOHIDE:
+                       SetIconMode(NIM_AUTO);
+                       break;
+
+                 case ID_SHOW_HIDDEN_ICONS:
+                       if (_pNotifyArea)
+                               SendMessage(*_pNotifyArea, WM_COMMAND, MAKEWPARAM(id,code), 0);
+                       break;
+
+                 case IDOK:
+                       EndDialog(_hwnd, id);
+                       break;
+
+                 case IDCANCEL:
+                        // rollback changes
+                       if (_pNotifyArea) {
+                                // restore original icon states and configuration data
+                               _pNotifyArea->_cfg = _cfg_org;
+                               _pNotifyArea->_show_hidden = _show_hidden_org;
+
+                               for(IconStateMap::const_iterator it=_icon_states_org.begin(); it!=_icon_states_org.end(); ++it) {
+                                       NotifyInfo& info = _pNotifyArea->_icon_map[it->first];
+
+                                       info._mode = it->second.first;
+                                       info._dwState = it->second.second;
+                               }
+
+                               SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
+                       }
+
+                       EndDialog(_hwnd, id);
+                       break;
+               }
+
+               return 0;
+       }
+
+       return 1;
+}
+
+int TrayNotifyDlg::Notify(int id, NMHDR* pnmh)
+{
+       switch(pnmh->code) {
+         case TVN_SELCHANGED: {
+               NMTREEVIEW* pnmtv = (NMTREEVIEW*)pnmh;
+               int idx = pnmtv->itemNew.lParam;
+
+               if (idx) {
+                       RefreshProperties(_info[idx]);
+                       _selectedItem = pnmtv->itemNew.hItem;
+               } else {
+                       /*
+                       SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
+                       SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
+                       SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
+                       */
+                       CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, 0);
+               }
+               break;}
+       }
+
+       return 0;
+}
+
+void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo& entry)
+{
+       SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, entry._tipText);
+       SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, entry._windowTitle);
+       SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, entry._modulePath);
+
+       CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, IDC_NOTIFY_SHOW+entry._mode);
+
+       String change_str;
+       if (entry._lastChange)
+               change_str.printf(TEXT("before %d s"), (GetTickCount()-entry._lastChange+500)/1000);
+       SetDlgItemText(_hwnd, IDC_LAST_CHANGE, change_str);
+
+       HICON hicon = 0; //get_window_icon_big(entry._hWnd, false);
+
+        // If we could not find an icon associated with the owner window, try to load one from the owning module.
+       if (!hicon && !entry._modulePath.empty()) {
+               hicon = ExtractIcon(g_Globals._hInstance, entry._modulePath, 0);
+
+               if (!hicon) {
+                       SHFILEINFO sfi;
+
+                       if (SHGetFileInfo(entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_LARGEICON))
+                               hicon = sfi.hIcon;
+               }
+       }
+
+       if (hicon) {
+               SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, (LPARAM)hicon, 0);
+               DestroyIcon(hicon);
+       } else
+               SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, 0, 0);
+}
+
+void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode)
+{
+       int idx = TreeView_GetItemData(_tree_ctrl, _selectedItem);
+
+       if (!idx)
+               return;
+
+       NotifyIconConfig& entry = _info[idx];
+
+       if (entry._mode != mode) {
+               entry._mode = mode;
+
+                // trigger refresh in notify area and this dialog
+               if (_pNotifyArea)
+                       SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
+       }
+
+       if (_pNotifyArea) {
+               bool found = false;
+
+               NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
+               for(NotifyIconCfgList::iterator it=cfg.begin(); it!=cfg.end(); ++it) {
+                       NotifyIconConfig& cfg_entry = *it;
+
+                       if (cfg_entry.match(entry)) {
+                               cfg_entry._mode = mode;
+                               ++found;
+                               break;
+                       }
+               }
+
+               if (!found) {
+                        // insert new configuration entry
+                       NotifyIconConfig cfg_entry = entry;
+
+                       cfg_entry._mode = mode;
+
+                       _pNotifyArea->_cfg.push_back(cfg_entry);
+               }
+       }
+
+       Refresh();
+       ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
+}
+
+
+ClockWindow::ClockWindow(HWND hwnd)
+ :     super(hwnd),
+       _tooltip(hwnd)
+{
+       *_time = TEXT('\0');
+       FormatTime();
+
+       _tooltip.add(_hwnd, _hwnd);
+}
+
+HWND ClockWindow::Create(HWND hwndParent)
+{
+       static BtnWindowClass wcClock(CLASSNAME_CLOCKWINDOW, CS_DBLCLKS);
+
+       ClientRect clnt(hwndParent);
+
+       WindowCanvas canvas(hwndParent);
+       FontSelection font(canvas, GetStockFont(ANSI_VAR_FONT));
+
+       RECT rect = {0, 0, 0, 0};
+       TCHAR buffer[16];
+       // Arbitrary high time so that the created clock window is big enough
+       SYSTEMTIME st = { 1601, 1, 0, 1, 23, 59, 59, 999 };
+
+       if (!GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
+               _tcscpy(buffer, TEXT("00:00"));
+
+       // Calculate the rectangle needed to draw the time (without actually drawing it)
+       DrawText(canvas, buffer, -1, &rect, DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
+       int clockwindowWidth = rect.right-rect.left + 4;
+
+       return Window::Create(WINDOW_CREATOR(ClockWindow), 0,
+                                                       wcClock, NULL, WS_CHILD|WS_VISIBLE,
+                                                       clnt.right-(clockwindowWidth), 1, clockwindowWidth, clnt.bottom-2, hwndParent);
+}
+
+LRESULT ClockWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
+{
+       switch(nmsg) {
+         case WM_PAINT:
+               Paint();
+               break;
+
+         case WM_LBUTTONDBLCLK:
+               launch_cpanel(_hwnd, TEXT("timedate.cpl"));
+               break;
+
+         default:
+               return super::WndProc(nmsg, wparam, lparam);
+       }
+
+       return 0;
+}
+
+int ClockWindow::Notify(int id, NMHDR* pnmh)
+{
+       if (pnmh->code == TTN_GETDISPINFO) {
+               LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
+
+               SYSTEMTIME systime;
+               TCHAR buffer[64];
+
+               GetLocalTime(&systime);
+
+               if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &systime, NULL, buffer, 64))
+                       _tcscpy(pdi->szText, buffer);
+               else
+                       pdi->szText[0] = '\0';
+       }
+
+       return 0;
+}
+
+void ClockWindow::TimerTick()
+{
+       if (FormatTime())
+               InvalidateRect(_hwnd, NULL, TRUE);      // refresh displayed time
+}
+
+bool ClockWindow::FormatTime()
+{
+       TCHAR buffer[16];
+
+       if (GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
+               if (_tcscmp(buffer, _time)) {
+                       _tcscpy(_time, buffer);
+                       return true;    // The text to display has changed.
+               }
+
+       return false;   // no change
+}
+
+void ClockWindow::Paint()
+{
+       PaintCanvas canvas(_hwnd);
+
+       FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
+
+       BkMode bkmode(canvas, TRANSPARENT);
+       FontSelection font(canvas, GetStockFont(ANSI_VAR_FONT));
+
+       DrawText(canvas, _time, -1, ClientRect(_hwnd), DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
+}