scroll mode for very long start menus in lean explorer branch
[reactos.git] / reactos / subsys / system / explorer / taskbar / startmenu.cpp
1 /*
2 * Copyright 2003, 2004 Martin Fuchs
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18
19
20 //
21 // Explorer clone, lean version
22 //
23 // startmenu.cpp
24 //
25 // Explorer start menu
26 //
27 // Martin Fuchs, 19.08.2003
28 //
29 // Credits: Thanks to Everaldo (http://www.everaldo.com) for his nice looking icons.
30 //
31
32
33 #include "../utility/utility.h"
34
35 #include "../explorer.h"
36 #include "../globals.h"
37 #include "../externals.h"
38 #include "../explorer_intres.h"
39
40 #include "desktopbar.h"
41 #include "startmenu.h"
42
43 #include "../dialogs/settings.h"
44
45
46 StartMenu::StartMenu(HWND hwnd)
47 : super(hwnd)
48 {
49 _next_id = IDC_FIRST_MENU;
50 _submenu_id = 0;
51
52 _border_left = 0;
53 _border_top = 0;
54 _bottom_max = INT_MAX;
55
56 _floating_btn = false;
57 _arrow_btns = false;
58 _scroll_mode = SCROLL_NOT;
59 _scroll_pos = 0;
60 _invisible_lines = 0;
61
62 _last_pos = WindowRect(hwnd).pos();
63 #ifdef _LIGHT_STARTMENU
64 _selected_id = -1;
65 _last_mouse_pos = 0;
66 #endif
67 }
68
69 StartMenu::StartMenu(HWND hwnd, const StartMenuCreateInfo& create_info)
70 : super(hwnd),
71 _create_info(create_info)
72 {
73 for(StartMenuFolders::const_iterator it=create_info._folders.begin(); it!=create_info._folders.end(); ++it)
74 if (*it)
75 _dirs.push_back(ShellDirectory(GetDesktopFolder(), *it, _hwnd));
76
77 _next_id = IDC_FIRST_MENU;
78 _submenu_id = 0;
79
80 _border_left = 0;
81 _border_top = create_info._border_top;
82 _bottom_max = INT_MAX;
83
84 _floating_btn = create_info._border_top? true: false;
85 _arrow_btns = false;
86 _scroll_mode = SCROLL_NOT;
87 _scroll_pos = 0;
88 _invisible_lines = 0;
89
90 _last_pos = WindowRect(hwnd).pos();
91 #ifdef _LIGHT_STARTMENU
92 _selected_id = -1;
93 _last_mouse_pos = 0;
94 #endif
95 }
96
97 StartMenu::~StartMenu()
98 {
99 SendParent(PM_STARTMENU_CLOSED);
100 }
101
102
103 // We need this wrapper function for s_wcStartMenu, it calls the WIN32 API,
104 // though static C++ initializers are not allowed for Winelib applications.
105 BtnWindowClass& StartMenu::GetWndClasss()
106 {
107 static BtnWindowClass s_wcStartMenu(CLASSNAME_STARTMENU);
108
109 return s_wcStartMenu;
110 }
111
112
113 Window::CREATORFUNC_INFO StartMenu::s_def_creator = STARTMENU_CREATOR(StartMenu);
114
115 HWND StartMenu::Create(int x, int y, const StartMenuFolders& folders, HWND hwndParent, LPCTSTR title, CREATORFUNC_INFO creator)
116 {
117 UINT style, ex_style;
118 int top_height;
119
120 if (hwndParent) {
121 style = WS_POPUP|WS_THICKFRAME|WS_CLIPCHILDREN|WS_VISIBLE;
122 ex_style = 0;
123 top_height = STARTMENU_TOP_BTN_SPACE;
124 } else {
125 style = WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_CLIPCHILDREN|WS_VISIBLE;
126 ex_style = WS_EX_TOOLWINDOW;
127 top_height = 0;
128 }
129
130 RECT rect = {x, y-STARTMENU_LINE_HEIGHT-top_height, x+STARTMENU_WIDTH_MIN, y};
131
132 #ifndef _LIGHT_STARTMENU
133 rect.top += STARTMENU_LINE_HEIGHT;
134 #endif
135
136 AdjustWindowRectEx(&rect, style, FALSE, ex_style);
137
138 StartMenuCreateInfo create_info;
139
140 create_info._folders = folders;
141 create_info._border_top = top_height;
142 create_info._creator = creator;
143
144 if (title)
145 create_info._title = title;
146
147 HWND hwnd = Window::Create(creator, &create_info, ex_style, GetWndClasss(), title,
148 style, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, hwndParent);
149
150 // make sure the window is not off the screen
151 MoveVisible(hwnd);
152
153 return hwnd;
154 }
155
156
157 LRESULT StartMenu::Init(LPCREATESTRUCT pcs)
158 {
159 try {
160 AddEntries();
161
162 if (super::Init(pcs))
163 return 1;
164
165 // create buttons for registered entries in _entries
166 for(ShellEntryMap::const_iterator it=_entries.begin(); it!=_entries.end(); ++it) {
167 const StartMenuEntry& sme = it->second;
168 bool hasSubmenu = false;
169
170 for(ShellEntrySet::const_iterator it=sme._entries.begin(); it!=sme._entries.end(); ++it)
171 if ((*it)->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
172 hasSubmenu = true;
173
174 #ifdef _LIGHT_STARTMENU
175 _buttons.push_back(SMBtnInfo(sme, it->first, hasSubmenu));
176 #else
177 AddButton(sme._title, sme._hIcon, hasSubmenu, it->first);
178 #endif
179 }
180
181 #ifdef _LIGHT_STARTMENU
182 if (_buttons.empty())
183 #else
184 if (!GetWindow(_hwnd, GW_CHILD))
185 #endif
186 AddButton(ResString(IDS_EMPTY), ICID_NONE, false, 0, false);
187
188 #ifdef _LIGHT_STARTMENU
189 ResizeToButtons();
190 #endif
191
192 #ifdef _LAZY_ICONEXTRACT
193 PostMessage(_hwnd, PM_UPDATE_ICONS, 0, 0);
194 #endif
195 } catch(COMException& e) {
196 HandleException(e, pcs->hwndParent); // destroys the start menu window while switching focus
197 }
198
199 return 0;
200 }
201
202 void StartMenu::AddEntries()
203 {
204 for(StartMenuShellDirs::iterator it=_dirs.begin(); it!=_dirs.end(); ++it) {
205 StartMenuDirectory& smd = *it;
206 ShellDirectory& dir = smd._dir;
207
208 if (!dir._scanned) {
209 WaitCursor wait;
210
211 #ifdef _LAZY_ICONEXTRACT
212 dir.smart_scan(SCAN_FILESYSTEM); // lazy icon extraction, try to read directly from filesystem
213 #else
214 dir.smart_scan(SCAN_EXTRACT_ICONS|SCAN_FILESYSTEM);
215 #endif
216 }
217
218 AddShellEntries(dir, -1, smd._subfolders);
219 }
220 }
221
222
223 void StartMenu::AddShellEntries(const ShellDirectory& dir, int max, bool subfolders)
224 {
225 int cnt = 0;
226
227 for(Entry*entry=dir._down; entry; entry=entry->_next) {
228 // hide files like "desktop.ini"
229 if (entry->_shell_attribs & SFGAO_HIDDEN)
230 //not appropriate for drive roots: if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
231 continue;
232
233 // hide subfolders if requested
234 if (!subfolders)
235 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
236 continue;
237
238 // only 'max' entries shall be added.
239 if (++cnt == max)
240 break;
241
242 if (entry->_etype == ET_SHELL)
243 AddEntry(dir._folder, static_cast<ShellEntry*>(entry));
244 else
245 AddEntry(dir._folder, entry);
246 }
247 }
248
249
250 LRESULT StartMenu::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
251 {
252 switch(nmsg) {
253 case WM_PAINT: {
254 PaintCanvas canvas(_hwnd);
255 Paint(canvas);
256 break;}
257
258 case WM_SIZE:
259 ResizeButtons(LOWORD(lparam)-_border_left);
260 break;
261
262 case WM_MOVE: {
263 POINTS& pos = MAKEPOINTS(lparam);
264
265 // move open submenus of floating menus
266 if (_submenu) {
267 int dx = pos.x - _last_pos.x;
268 int dy = pos.y - _last_pos.y;
269
270 if (dx || dy) {
271 WindowRect rt(_submenu);
272 SetWindowPos(_submenu, 0, rt.left+dx, rt.top+dy, 0, 0, SWP_NOSIZE|SWP_NOACTIVATE);
273 //MoveVisible(_submenu);
274 }
275 }
276
277 _last_pos.x = pos.x;
278 _last_pos.y = pos.y;
279 goto def;}
280
281 case WM_NCHITTEST: {
282 LRESULT res = super::WndProc(nmsg, wparam, lparam);
283
284 if (res>=HTSIZEFIRST && res<=HTSIZELAST)
285 return HTCLIENT; // disable window resizing
286
287 return res;}
288
289 case WM_LBUTTONDOWN: {
290 RECT rect;
291
292 // check mouse cursor for coordinates of floating button
293 GetFloatingButtonRect(&rect);
294
295 if (PtInRect(&rect, Point(lparam))) {
296 // create a floating copy of the current start menu
297 WindowRect pos(_hwnd);
298
299 ///@todo do something similar to StartMenuRoot::TrackStartmenu() in order to automatically close submenus when clicking on the desktop background
300 StartMenu::Create(pos.left+3, pos.bottom-3, _create_info._folders, 0, _create_info._title, _create_info._creator);
301 CloseStartMenu();
302 }
303
304 #ifdef _LIGHT_STARTMENU
305 int id = ButtonHitTest(Point(lparam));
306
307 if (id)
308 Command(id, BN_CLICKED);
309 #endif
310 break;}
311
312 case WM_SYSCOMMAND:
313 if ((wparam&0xFFF0) == SC_SIZE)
314 return 0; // disable window resizing
315 goto def;
316
317 case WM_ACTIVATEAPP:
318 // close start menu when activating another application
319 if (!wparam)
320 CloseStartMenu();
321 break; // don't call super::WndProc in case "this" has been deleted
322
323 case WM_CANCELMODE:
324 CloseStartMenu();
325
326 #ifdef _LIGHT_STARTMENU
327 if (_scroll_mode != SCROLL_NOT) {
328 ReleaseCapture();
329 KillTimer(_hwnd, 0);
330 }
331 #endif
332 break;
333
334 #ifdef _LIGHT_STARTMENU
335 case WM_MOUSEMOVE: {
336 // automatically set the focus to startmenu entries when moving the mouse over them
337 if (lparam != _last_mouse_pos) { // don't process WM_MOUSEMOVE when opening submenus using keyboard navigation
338 Point pt(lparam);
339
340 if (_arrow_btns) {
341 RECT rect_up, rect_down;
342
343 GetArrowButtonRects(&rect_up, &rect_down);
344
345 SCROLL_MODE scroll_mode = SCROLL_NOT;
346
347 if (PtInRect(&rect_up, pt))
348 scroll_mode = SCROLL_UP;
349 else if (PtInRect(&rect_down, pt))
350 scroll_mode = SCROLL_DOWN;
351
352 if (scroll_mode != _scroll_mode) {
353 if (scroll_mode == SCROLL_NOT) {
354 ReleaseCapture();
355 KillTimer(_hwnd, 0);
356 } else {
357 CloseSubmenus();
358 SetTimer(_hwnd, 0, 150, NULL); // 150 ms scroll interval
359 SetCapture(_hwnd);
360 }
361
362 _scroll_mode = scroll_mode;
363 }
364 }
365
366 int new_id = ButtonHitTest(pt);
367
368 if (new_id != _selected_id)
369 SelectButton(new_id);
370
371 _last_mouse_pos = lparam;
372 }
373 break;}
374
375 case WM_TIMER:
376 if (_scroll_mode == SCROLL_UP) {
377 if (_scroll_pos > 0) {
378 --_scroll_pos;
379 InvalidateRect(_hwnd, NULL, TRUE);
380 }
381 } else {
382 if (_scroll_pos <= _invisible_lines) {
383 ++_scroll_pos;
384 InvalidateRect(_hwnd, NULL, TRUE);
385 }
386 }
387 break;
388
389 case WM_KEYDOWN:
390 ProcessKey(wparam);
391 break;
392 #else
393 case PM_STARTENTRY_FOCUSED: { ///@todo use TrackMouseEvent() and WM_MOUSEHOVER to wait a bit before opening submenus
394 BOOL hasSubmenu = wparam;
395 HWND hctrl = (HWND)lparam;
396
397 // automatically open submenus
398 if (hasSubmenu) {
399 UpdateWindow(_hwnd); // draw focused button before waiting on submenu creation
400 //SendMessage(_hwnd, WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hctrl),BN_CLICKED), (LPARAM)hctrl);
401 Command(GetDlgCtrlID(hctrl), BN_CLICKED);
402 } else {
403 // close any open submenu
404 CloseOtherSubmenus();
405 }
406 break;}
407 #endif
408
409 #ifdef _LAZY_ICONEXTRACT
410 case PM_UPDATE_ICONS:
411 UpdateIcons(/*wparam*/);
412 break;
413 #endif
414
415 case PM_STARTENTRY_LAUNCHED:
416 if (GetWindowStyle(_hwnd) & WS_CAPTION) // don't automatically close floating menus
417 return 0;
418
419 // route message to the parent menu and close menus after launching an entry
420 if (!SendParent(nmsg, wparam, lparam))
421 CloseStartMenu();
422 return 1; // signal that we have received and processed the message
423
424 case PM_STARTMENU_CLOSED:
425 _submenu = 0;
426 break;
427
428 case PM_SELECT_ENTRY:
429 SelectButtonIndex(0, wparam?true:false);
430 break;
431
432 default: def:
433 return super::WndProc(nmsg, wparam, lparam);
434 }
435
436 return 0;
437 }
438
439
440 #ifdef _LIGHT_STARTMENU
441
442 int StartMenu::ButtonHitTest(POINT pt)
443 {
444 ClientRect clnt(_hwnd);
445 RECT rect = {_border_left, _border_top, clnt.right, STARTMENU_LINE_HEIGHT};
446
447 if (pt.x<rect.left || pt.x>rect.right)
448 return 0;
449
450 for(SMBtnVector::const_iterator it=_buttons.begin()+_scroll_pos; it!=_buttons.end(); ++it) {
451 const SMBtnInfo& info = *it;
452
453 if (rect.top > pt.y)
454 break;
455
456 rect.bottom = rect.top + (info._id==-1? STARTMENU_SEP_HEIGHT: STARTMENU_LINE_HEIGHT);
457
458 if (rect.bottom > _bottom_max)
459 break;
460
461 if (pt.y < rect.bottom) // PtInRect(&rect, pt)
462 return info._id;
463
464 rect.top = rect.bottom;
465 }
466
467 return 0;
468 }
469
470 void StartMenu::InvalidateSelection()
471 {
472 if (!_selected_id)
473 return;
474
475 ClientRect clnt(_hwnd);
476 RECT rect = {_border_left, _border_top, clnt.right, STARTMENU_LINE_HEIGHT};
477
478 for(SMBtnVector::const_iterator it=_buttons.begin()+_scroll_pos; it!=_buttons.end(); ++it) {
479 const SMBtnInfo& info = *it;
480
481 rect.bottom = rect.top + (info._id==-1? STARTMENU_SEP_HEIGHT: STARTMENU_LINE_HEIGHT);
482
483 if (info._id == _selected_id) {
484 InvalidateRect(_hwnd, &rect, TRUE);
485 break;
486 }
487
488 rect.top = rect.bottom;
489 }
490 }
491
492 const SMBtnInfo* StartMenu::GetButtonInfo(int id) const
493 {
494 for(SMBtnVector::const_iterator it=_buttons.begin(); it!=_buttons.end(); ++it)
495 if (it->_id == id)
496 return &*it;
497
498 return NULL;
499 }
500
501 bool StartMenu::SelectButton(int id, bool open_sub)
502 {
503 if (id == -1)
504 return false;
505
506 if (id == _selected_id)
507 return true;
508
509 InvalidateSelection();
510
511 const SMBtnInfo* btn = GetButtonInfo(id);
512
513 if (btn && btn->_enabled) {
514 _selected_id = id;
515
516 InvalidateSelection();
517
518 // automatically open submenus
519 if (btn->_hasSubmenu) {
520 if (open_sub)
521 OpenSubmenu();
522 } else
523 CloseOtherSubmenus(); // close any open submenu
524
525 return true;
526 } else {
527 _selected_id = -1;
528 return false;
529 }
530 }
531
532 bool StartMenu::OpenSubmenu(bool select_first)
533 {
534 if (_selected_id == -1)
535 return false;
536
537 InvalidateSelection();
538
539 const SMBtnInfo* btn = GetButtonInfo(_selected_id);
540
541 // automatically open submenus
542 if (btn->_hasSubmenu) {
543 //@@ allows destroying of startmenu when processing PM_UPDATE_ICONS -> GPF
544 UpdateWindow(_hwnd); // draw focused button before waiting on submenu creation
545 Command(_selected_id, BN_CLICKED);
546
547 if (select_first && _submenu)
548 SendMessage(_submenu, PM_SELECT_ENTRY, (WPARAM)false, 0);
549
550 return true;
551 } else
552 return false;
553 }
554
555
556 int StartMenu::GetSelectionIndex()
557 {
558 if (_selected_id == -1)
559 return -1;
560
561 for(int i=0; i<(int)_buttons.size(); ++i)
562 if (_buttons[i]._id == _selected_id)
563 return i;
564
565 return -1;
566 }
567
568 bool StartMenu::SelectButtonIndex(int idx, bool open_sub)
569 {
570 if (idx>=0 && idx<(int)_buttons.size())
571 return SelectButton(_buttons[idx]._id, open_sub);
572 else
573 return false;
574 }
575
576 void StartMenu::ProcessKey(int vk)
577 {
578 switch(vk) {
579 case VK_RETURN:
580 if (_selected_id)
581 Command(_selected_id, BN_CLICKED);
582 break;
583
584 case VK_UP:
585 Navigate(-1);
586 break;
587
588 case VK_DOWN:
589 Navigate(+1);
590 break;
591
592 case VK_HOME:
593 SelectButtonIndex(0, false);
594 break;
595
596 case VK_END:
597 SelectButtonIndex(_buttons.size()-1, false);
598 break;
599
600 case VK_LEFT:
601 if (_submenu)
602 CloseOtherSubmenus();
603 else if (!(GetWindowStyle(_hwnd) & WS_CAPTION)) // don't automatically close floating menus
604 DestroyWindow(_hwnd);
605 break;
606
607 case VK_RIGHT:
608 OpenSubmenu(true);
609 break;
610
611 case VK_ESCAPE:
612 CloseStartMenu();
613 break;
614
615 default:
616 if (vk>='0' && vk<='Z')
617 JumpToNextShortcut(vk);
618 }
619 }
620
621 bool StartMenu::Navigate(int step)
622 {
623 int idx = GetSelectionIndex();
624
625 if (idx == -1)
626 if (step > 0)
627 idx = 0 - step;
628 else
629 idx = _buttons.size() - step;
630
631 for(;;) {
632 idx += step;
633
634 if (idx<0 || idx>(int)_buttons.size())
635 break;
636
637 if (SelectButtonIndex(idx, false))
638 return true;
639 }
640
641 return false;
642 }
643
644 bool StartMenu::JumpToNextShortcut(char c)
645 {
646 int cur_idx = GetSelectionIndex();
647
648 if (cur_idx == -1)
649 cur_idx = 0;
650
651 int first_found = 0;
652 int found_more = 0;
653
654 SMBtnVector::const_iterator cur_it = _buttons.begin();
655 cur_it += cur_idx + 1;
656
657 // first search down from current item...
658 SMBtnVector::const_iterator it = cur_it;
659 for(; it!=_buttons.end(); ++it) {
660 const SMBtnInfo& btn = *it;
661
662 if (!btn._title.empty() && toupper((TBYTE)btn._title.at(0)) == c) {
663 if (!first_found)
664 first_found = btn._id;
665 else
666 ++found_more;
667 }
668 }
669
670 // ...now search from top to the current item
671 it = _buttons.begin();
672 for(; it!=_buttons.end() && it!=cur_it; ++it) {
673 const SMBtnInfo& btn = *it;
674
675 if (!btn._title.empty() && toupper((TBYTE)btn._title.at(0)) == c) {
676 if (!first_found)
677 first_found = btn._id;
678 else
679 ++found_more;
680 }
681 }
682
683 if (first_found) {
684 SelectButton(first_found);
685
686 if (!found_more)
687 Command(first_found, BN_CLICKED);
688
689 return true;
690 } else
691 return false;
692 }
693
694 #endif // _LIGHT_STARTMENU
695
696
697 bool StartMenu::GetButtonRect(int id, PRECT prect) const
698 {
699 #ifdef _LIGHT_STARTMENU
700 ClientRect clnt(_hwnd);
701 RECT rect = {_border_left, _border_top, clnt.right, STARTMENU_LINE_HEIGHT};
702
703 for(SMBtnVector::const_iterator it=_buttons.begin()+_scroll_pos; it!=_buttons.end(); ++it) {
704 const SMBtnInfo& info = *it;
705
706 rect.bottom = rect.top + (info._id==-1? STARTMENU_SEP_HEIGHT: STARTMENU_LINE_HEIGHT);
707
708 if (info._id == id) {
709 *prect = rect;
710 return true;
711 }
712
713 rect.top = rect.bottom;
714 }
715
716 return false;
717 #else
718 HWND btn = GetDlgItem(_hwnd, id);
719
720 if (btn) {
721 GetWindowRect(btn, prect);
722 ScreenToClient(_hwnd, prect);
723
724 return true;
725 } else
726 return false;
727 #endif
728 }
729
730
731 void StartMenu::DrawFloatingButton(HDC hdc)
732 {
733 static ResIconEx floatingIcon(IDI_FLOATING, 8, 4);
734
735 ClientRect clnt(_hwnd);
736
737 DrawIconEx(hdc, clnt.right-12, 0, floatingIcon, 8, 4, 0, 0, DI_NORMAL);
738 }
739
740 void StartMenu::GetFloatingButtonRect(LPRECT prect)
741 {
742 GetClientRect(_hwnd, prect);
743
744 prect->right -= 4;
745 prect->left = prect->right - 8;
746 prect->bottom = 4;
747 }
748
749
750 void StartMenu::DrawArrows(HDC hdc)
751 {
752 static ResIconEx arrowUpIcon(IDI_ARROW_UP, 8, 4);
753 static ResIconEx arrowDownIcon(IDI_ARROW_DOWN, 8, 4);
754
755 ClientRect clnt(_hwnd);
756
757 DrawIconEx(hdc, clnt.right/2-4, _floating_btn?3:1, arrowUpIcon, 8, 4, 0, 0, DI_NORMAL);
758 DrawIconEx(hdc, clnt.right/2-4, clnt.bottom-5, arrowDownIcon, 8, 4, 0, 0, DI_NORMAL);
759 }
760
761 void StartMenu::GetArrowButtonRects(LPRECT prect_up, LPRECT prect_down)
762 {
763 GetClientRect(_hwnd, prect_up);
764 *prect_down = *prect_up;
765
766 // prect_up->left = prect_up->right/2 - 4;
767 // prect_up->right = prect_up->left + 8;
768 prect_up->right -= 8;
769 prect_up->top = _floating_btn? 3: 1;
770 prect_up->bottom = prect_up->top + 4;
771
772 // prect_down->left = prect_down->right/2 - 4;
773 // prect_down->right = prect_down->left + 8;
774 prect_down->right -= 8;
775 prect_down->top = prect_down->bottom - 5;
776 }
777
778
779 void StartMenu::Paint(PaintCanvas& canvas)
780 {
781 if (_floating_btn)
782 DrawFloatingButton(canvas);
783
784 #ifdef _LIGHT_STARTMENU
785 if (_arrow_btns)
786 DrawArrows(canvas);
787
788 ClientRect clnt(_hwnd);
789 RECT rect = {_border_left, _border_top, clnt.right, STARTMENU_LINE_HEIGHT};
790
791 int sep_width = rect.right-rect.left - 4;
792
793 FontSelection font(canvas, GetStockFont(DEFAULT_GUI_FONT));
794 BkMode bk_mode(canvas, TRANSPARENT);
795
796 for(SMBtnVector::const_iterator it=_buttons.begin()+_scroll_pos; it!=_buttons.end(); ++it) {
797 const SMBtnInfo& btn = *it;
798
799 if (rect.top > canvas.rcPaint.bottom)
800 break;
801
802 if (btn._id == -1) { // a separator?
803 rect.bottom = rect.top + STARTMENU_SEP_HEIGHT;
804
805 if (rect.bottom > _bottom_max)
806 break;
807
808 BrushSelection brush_sel(canvas, GetSysColorBrush(COLOR_BTNSHADOW));
809 PatBlt(canvas, rect.left+2, rect.top+STARTMENU_SEP_HEIGHT/2-1, sep_width, 1, PATCOPY);
810
811 SelectBrush(canvas, GetSysColorBrush(COLOR_BTNHIGHLIGHT));
812 PatBlt(canvas, rect.left+2, rect.top+STARTMENU_SEP_HEIGHT/2, sep_width, 1, PATCOPY);
813 } else {
814 rect.bottom = rect.top + STARTMENU_LINE_HEIGHT;
815
816 if (rect.bottom > _bottom_max)
817 break;
818
819 if (rect.top >= canvas.rcPaint.top)
820 DrawStartMenuButton(canvas, rect, btn._title, btn, btn._id==_selected_id, false);
821 }
822
823 rect.top = rect.bottom;
824 }
825 #endif
826 }
827
828 #ifdef _LAZY_ICONEXTRACT
829 void StartMenu::UpdateIcons(/*int idx*/)
830 {
831 UpdateWindow(_hwnd);
832
833 #ifdef _SINGLE_ICONEXTRACT
834
835 //if (idx >= 0)
836 int idx = _scroll_pos;
837
838 for(; idx<(int)_buttons.size(); ++idx) {
839 SMBtnInfo& btn = _buttons[idx];
840
841 if (btn._icon_id==ICID_UNKNOWN && btn._id>0) {
842 StartMenuEntry& sme = _entries[btn._id];
843
844 btn._icon_id = ICID_NONE;
845
846 for(ShellEntrySet::iterator it=sme._entries.begin(); it!=sme._entries.end(); ++it) {
847 Entry* entry = *it;
848
849 if (entry->_icon_id == ICID_UNKNOWN)
850 try {
851 entry->extract_icon();
852 } catch(COMException&) {
853 // ignore unexpected exceptions while extracting icons
854 }
855
856 if (entry->_icon_id > ICID_NONE) {
857 btn._icon_id = (ICON_ID)/*@@*/ entry->_icon_id;
858
859 RECT rect;
860
861 GetButtonRect(btn._id, &rect);
862
863 if (rect.bottom > _bottom_max)
864 break;
865
866 WindowCanvas canvas(_hwnd);
867 DrawStartMenuButton(canvas, rect, NULL, btn, btn._id==_selected_id, false);
868
869 //InvalidateRect(_hwnd, &rect, FALSE);
870 //UpdateWindow(_hwnd);
871 //break;
872
873 break;
874 }
875 }
876 }
877 }
878
879 // if (++idx < (int)_buttons.size())
880 // PostMessage(_hwnd, PM_UPDATE_ICONS, idx, 0);
881
882 #else
883
884 int icons_extracted = 0;
885 int icons_updated = 0;
886
887 for(StartMenuShellDirs::iterator it=_dirs.begin(); it!=_dirs.end(); ++it) {
888 ShellDirectory& dir = it->_dir;
889
890 icons_extracted += dir.extract_icons();
891 }
892
893 if (icons_extracted) {
894 for(ShellEntryMap::iterator it1=_entries.begin(); it1!=_entries.end(); ++it1) {
895 StartMenuEntry& sme = it1->second;
896
897 if (!sme._hIcon) {
898 sme._hIcon = (HICON)-1;
899
900 for(ShellEntrySet::const_iterator it2=sme._entries.begin(); it2!=sme._entries.end(); ++it2) {
901 const Entry* sm_entry = *it2;
902
903 if (sm_entry->_hIcon) {
904 sme._hIcon = sm_entry->_hIcon;
905 break;
906 }
907 }
908 }
909 }
910
911 for(SMBtnVector::iterator it=_buttons.begin(); it!=_buttons.end(); ++it) {
912 SMBtnInfo& info = *it;
913
914 if (info._id>0 && !info._hIcon) {
915 info._hIcon = _entries[info._id]._hIcon;
916 ++icons_updated;
917 }
918 }
919 }
920
921 if (icons_updated) {
922 InvalidateRect(_hwnd, NULL, FALSE);
923 UpdateWindow(_hwnd);
924 }
925 #endif
926 }
927 #endif
928
929
930 // resize child button controls to accomodate for new window size
931 void StartMenu::ResizeButtons(int cx)
932 {
933 HDWP hdwp = BeginDeferWindowPos(10);
934
935 for(HWND ctrl=GetWindow(_hwnd,GW_CHILD); ctrl; ctrl=GetNextWindow(ctrl,GW_HWNDNEXT)) {
936 ClientRect rt(ctrl);
937
938 if (rt.right != cx) {
939 int height = rt.bottom - rt.top;
940
941 // special handling for separator controls
942 if (!height && (GetWindowStyle(ctrl)&SS_TYPEMASK)==SS_ETCHEDHORZ)
943 height = 2;
944
945 hdwp = DeferWindowPos(hdwp, ctrl, 0, 0, 0, cx, height, SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
946 }
947 }
948
949 EndDeferWindowPos(hdwp);
950 }
951
952
953 int StartMenu::Command(int id, int code)
954 {
955 #ifndef _LIGHT_STARTMENU
956 switch(id) {
957 case IDCANCEL:
958 CloseStartMenu(id);
959 break;
960
961 default: {
962 #endif
963 ShellEntryMap::iterator found = _entries.find(id);
964
965 if (found != _entries.end()) {
966 ActivateEntry(id, found->second._entries);
967 return 0;
968 }
969
970 return super::Command(id, code);
971 #ifndef _LIGHT_STARTMENU
972 }
973 }
974
975 return 0;
976 #endif
977 }
978
979
980 StartMenuEntry& StartMenu::AddEntry(const String& title, ICON_ID icon_id, Entry* entry)
981 {
982 // search for an already existing subdirectory entry with the same name
983 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
984 for(ShellEntryMap::iterator it=_entries.begin(); it!=_entries.end(); ++it) {
985 StartMenuEntry& sme = it->second;
986
987 if (sme._title == title) ///@todo speed up by using a map indexed by name
988 for(ShellEntrySet::iterator it2=sme._entries.begin(); it2!=sme._entries.end(); ++it2) {
989 if ((*it2)->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
990 // merge the new shell entry with the existing of the same name
991 sme._entries.insert(entry);
992 return sme;
993 }
994 }
995 }
996
997 StartMenuEntry& sme = AddEntry(title, icon_id);
998
999 sme._entries.insert(entry);
1000
1001 return sme;
1002 }
1003
1004 StartMenuEntry& StartMenu::AddEntry(const String& title, ICON_ID icon_id, int id)
1005 {
1006 if (id == -1)
1007 id = ++_next_id;
1008
1009 StartMenuEntry& sme = _entries[id];
1010
1011 sme._title = title;
1012 sme._icon_id = icon_id;
1013
1014 return sme;
1015 }
1016
1017 StartMenuEntry& StartMenu::AddEntry(const ShellFolder folder, ShellEntry* entry)
1018 {
1019 ICON_ID icon_id;
1020
1021 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1022 icon_id = ICID_FOLDER;
1023 else
1024 icon_id = (ICON_ID)/*@@*/ entry->_icon_id;
1025
1026 return AddEntry(folder.get_name(entry->_pidl), icon_id, entry);
1027 }
1028
1029 StartMenuEntry& StartMenu::AddEntry(const ShellFolder folder, Entry* entry)
1030 {
1031 ICON_ID icon_id;
1032
1033 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1034 icon_id = ICID_FOLDER;
1035 else
1036 icon_id = (ICON_ID)/*@@*/ entry->_icon_id;
1037
1038 return AddEntry(entry->_display_name, icon_id, entry);
1039 }
1040
1041
1042 void StartMenu::AddButton(LPCTSTR title, ICON_ID icon_id, bool hasSubmenu, int id, bool enabled)
1043 {
1044 #ifdef _LIGHT_STARTMENU
1045 _buttons.push_back(SMBtnInfo(title, icon_id, id, hasSubmenu, enabled));
1046 #else
1047 DWORD style = enabled? WS_VISIBLE|WS_CHILD|BS_OWNERDRAW: WS_VISIBLE|WS_CHILD|BS_OWNERDRAW|WS_DISABLED;
1048
1049 WindowRect rect(_hwnd);
1050 ClientRect clnt(_hwnd);
1051
1052 // increase window height to make room for the new button
1053 rect.top -= STARTMENU_LINE_HEIGHT;
1054
1055 // move down if we are too high now
1056 if (rect.top < 0) {
1057 rect.top += STARTMENU_LINE_HEIGHT;
1058 rect.bottom += STARTMENU_LINE_HEIGHT;
1059 }
1060
1061 WindowCanvas canvas(_hwnd);
1062 FontSelection font(canvas, GetStockFont(DEFAULT_GUI_FONT));
1063
1064 // widen window, if it is too small
1065 int text_width = GetStartMenuBtnTextWidth(canvas, title, _hwnd) + 16/*icon*/ + 10/*placeholder*/ + 16/*arrow*/;
1066
1067 int cx = clnt.right - _border_left;
1068 if (text_width > cx)
1069 rect.right += text_width-cx;
1070
1071 MoveWindow(_hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE);
1072
1073 StartMenuCtrl(_hwnd, _border_left, clnt.bottom, rect.right-rect.left-_border_left,
1074 title, id, g_Globals._icon_cache.get_icon(icon_id)._hIcon, hasSubmenu, style);
1075 #endif
1076 }
1077
1078 void StartMenu::AddSeparator()
1079 {
1080 #ifdef _LIGHT_STARTMENU
1081 _buttons.push_back(SMBtnInfo(NULL, ICID_NONE, -1, false));
1082 #else
1083 WindowRect rect(_hwnd);
1084 ClientRect clnt(_hwnd);
1085
1086 // increase window height to make room for the new separator
1087 rect.top -= STARTMENU_SEP_HEIGHT;
1088
1089 // move down if we are too high now
1090 if (rect.top < 0) {
1091 rect.top += STARTMENU_LINE_HEIGHT;
1092 rect.bottom += STARTMENU_LINE_HEIGHT;
1093 }
1094
1095 MoveWindow(_hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE);
1096
1097 StartMenuSeparator(_hwnd, _border_left, clnt.bottom, rect.right-rect.left-_border_left);
1098 #endif
1099 }
1100
1101
1102 bool StartMenu::CloseOtherSubmenus(int id)
1103 {
1104 if (_submenu) {
1105 if (IsWindow(_submenu)) {
1106 if (_submenu_id == id)
1107 return false;
1108 else {
1109 _submenu_id = 0;
1110 DestroyWindow(_submenu);
1111 // _submenu should be reset automatically by PM_STARTMENU_CLOSED, but safety first...
1112 }
1113 }
1114
1115 _submenu = 0;
1116 }
1117
1118 return true;
1119 }
1120
1121
1122 void StartMenu::CreateSubmenu(int id, LPCTSTR title, CREATORFUNC_INFO creator)
1123 {
1124 CreateSubmenu(id, StartMenuFolders(), title, creator);
1125 }
1126
1127 bool StartMenu::CreateSubmenu(int id, int folder_id, LPCTSTR title, CREATORFUNC_INFO creator)
1128 {
1129 try {
1130 SpecialFolderPath folder(folder_id, _hwnd);
1131
1132 StartMenuFolders new_folders;
1133 new_folders.push_back(folder);
1134
1135 CreateSubmenu(id, new_folders, title, creator);
1136
1137 return true;
1138 } catch(COMException&) {
1139 // ignore Exception and don't display anything
1140 CloseOtherSubmenus(id);
1141 _buttons[GetSelectionIndex()]._enabled = false; // disable entries for non-existing folders
1142 return false;
1143 }
1144 }
1145
1146 bool StartMenu::CreateSubmenu(int id, int folder_id1, int folder_id2, LPCTSTR title, CREATORFUNC_INFO creator)
1147 {
1148 StartMenuFolders new_folders;
1149
1150 try {
1151 new_folders.push_back(SpecialFolderPath(folder_id1, _hwnd));
1152 } catch(COMException&) {
1153 }
1154
1155 try {
1156 new_folders.push_back(SpecialFolderPath(folder_id2, _hwnd));
1157 } catch(COMException&) {
1158 }
1159
1160 if (!new_folders.empty()) {
1161 CreateSubmenu(id, new_folders, title, creator);
1162 return true;
1163 } else {
1164 CloseOtherSubmenus(id);
1165 _buttons[GetSelectionIndex()]._enabled = false; // disable entries for non-existing folders
1166 return false;
1167 }
1168 }
1169
1170 void StartMenu::CreateSubmenu(int id, const StartMenuFolders& new_folders, LPCTSTR title, CREATORFUNC_INFO creator)
1171 {
1172 // Only open one submenu at a time.
1173 if (!CloseOtherSubmenus(id))
1174 return;
1175
1176 RECT rect;
1177 int x, y;
1178
1179 if (GetButtonRect(id, &rect)) {
1180 ClientToScreen(_hwnd, &rect);
1181
1182 x = rect.right; // Submenus should overlap their parent a bit.
1183 y = rect.top+STARTMENU_LINE_HEIGHT +_border_top/*own border*/ -STARTMENU_TOP_BTN_SPACE/*border of new submenu*/;
1184 } else {
1185 WindowRect pos(_hwnd);
1186
1187 x = pos.right;
1188 y = pos.top;
1189 }
1190
1191 _submenu_id = id;
1192 _submenu = StartMenu::Create(x, y, new_folders, _hwnd, title, creator);
1193 }
1194
1195
1196 void StartMenu::ActivateEntry(int id, const ShellEntrySet& entries)
1197 {
1198 StartMenuFolders new_folders;
1199 String title;
1200
1201 for(ShellEntrySet::const_iterator it=entries.begin(); it!=entries.end(); ++it) {
1202 Entry* entry = const_cast<Entry*>(*it);
1203
1204 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1205
1206 ///@todo If the user explicitly clicked on a submenu, display this folder as floating start menu.
1207
1208 if (entry->_etype == ET_SHELL)
1209 new_folders.push_back(entry->create_absolute_pidl());
1210 else {
1211 TCHAR path[MAX_PATH];
1212
1213 if (entry->get_path(path))
1214 new_folders.push_back(path);
1215 }
1216
1217 if (title.empty())
1218 title = entry->_display_name;
1219 } else {
1220 // The entry is no subdirectory, so there can only be one shell entry.
1221 assert(entries.size()==1);
1222
1223 HWND hparent = GetParent(_hwnd);
1224 ShellPath shell_path = entry->create_absolute_pidl();
1225
1226 // close start menus after launching the selected entry
1227 CloseStartMenu(id);
1228
1229 ///@todo launch in the background; specify correct HWND for error message box titles
1230 SHELLEXECUTEINFO shexinfo;
1231
1232 shexinfo.cbSize = sizeof(SHELLEXECUTEINFO);
1233 shexinfo.fMask = SEE_MASK_INVOKEIDLIST; // SEE_MASK_IDLIST is also possible.
1234 shexinfo.hwnd = hparent;
1235 shexinfo.lpVerb = NULL;
1236 shexinfo.lpFile = NULL;
1237 shexinfo.lpParameters = NULL;
1238 shexinfo.lpDirectory = NULL;
1239 shexinfo.nShow = SW_SHOWNORMAL;
1240
1241 shexinfo.lpIDList = &*shell_path;
1242
1243 // add PIDL to the recent file list
1244 SHAddToRecentDocs(SHARD_PIDL, shexinfo.lpIDList);
1245
1246 if (!ShellExecuteEx(&shexinfo))
1247 display_error(hparent, GetLastError());
1248
1249 // we may have deleted 'this' - ensure we leave the loop and function
1250 return;
1251 }
1252 }
1253
1254 if (!new_folders.empty()) {
1255 // Only open one submenu at a time.
1256 if (!CloseOtherSubmenus(id))
1257 return;
1258
1259 CreateSubmenu(id, new_folders, title);
1260 }
1261 }
1262
1263
1264 /// close all windows of the start menu popup
1265 void StartMenu::CloseStartMenu(int id)
1266 {
1267 if (!(GetWindowStyle(_hwnd) & WS_CAPTION)) { // don't automatically close floating menus
1268 if (!SendParent(PM_STARTENTRY_LAUNCHED, id, (LPARAM)_hwnd))
1269 DestroyWindow(_hwnd);
1270 } else if (_submenu) // instead close submenus of floating parent menus
1271 CloseSubmenus();
1272 }
1273
1274
1275 int GetStartMenuBtnTextWidth(HDC hdc, LPCTSTR title, HWND hwnd)
1276 {
1277 RECT rect = {0, 0, 0, 0};
1278 DrawText(hdc, title, -1, &rect, DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
1279
1280 return rect.right-rect.left;
1281 }
1282
1283 #ifdef _LIGHT_STARTMENU
1284 void DrawStartMenuButton(HDC hdc, const RECT& rect, LPCTSTR title, const SMBtnInfo& btn, bool has_focus, bool pushed)
1285 #else
1286 void DrawStartMenuButton(HDC hdc, const RECT& rect, LPCTSTR title, HICON hIcon,
1287 bool hasSubmenu, bool enabled, bool has_focus, bool pushed);
1288 #endif
1289 {
1290 UINT style = DFCS_BUTTONPUSH;
1291
1292 if (!btn._enabled)
1293 style |= DFCS_INACTIVE;
1294
1295 POINT iconPos = {rect.left+2, (rect.top+rect.bottom-16)/2};
1296 RECT textRect = {rect.left+16+4, rect.top+2, rect.right-4, rect.bottom-4};
1297
1298 if (pushed) {
1299 style |= DFCS_PUSHED;
1300 ++iconPos.x; ++iconPos.y;
1301 ++textRect.left; ++textRect.top;
1302 ++textRect.right; ++textRect.bottom;
1303 }
1304
1305 int bk_color_idx = COLOR_BTNFACE;
1306 int text_color_idx = COLOR_BTNTEXT;
1307
1308 if (has_focus) {
1309 bk_color_idx = COLOR_HIGHLIGHT;
1310 text_color_idx = COLOR_HIGHLIGHTTEXT;
1311 }
1312
1313 COLORREF bk_color = GetSysColor(bk_color_idx);
1314 HBRUSH bk_brush = GetSysColorBrush(bk_color_idx);
1315
1316 if (title)
1317 FillRect(hdc, &rect, bk_brush);
1318
1319 if (btn._icon_id > ICID_NONE)
1320 g_Globals._icon_cache.get_icon(btn._icon_id).draw(hdc, iconPos.x, iconPos.y, 16, 16, bk_color, bk_brush);
1321
1322 // draw submenu arrow at the right
1323 if (btn._hasSubmenu) {
1324 static SmallIcon arrowIcon(IDI_ARROW);
1325 static SmallIcon selArrowIcon(IDI_ARROW_SELECTED);
1326
1327 DrawIconEx(hdc, rect.right-16, iconPos.y,
1328 has_focus? selArrowIcon: arrowIcon,
1329 16, 16, 0, bk_brush, DI_NORMAL);
1330 }
1331
1332 if (title) {
1333 BkMode bk_mode(hdc, TRANSPARENT);
1334
1335 if (!btn._enabled) // dis->itemState & (ODS_DISABLED|ODS_GRAYED)
1336 DrawGrayText(hdc, &textRect, title, DT_SINGLELINE|DT_NOPREFIX|DT_VCENTER);
1337 else {
1338 TextColor lcColor(hdc, GetSysColor(text_color_idx));
1339 DrawText(hdc, title, -1, &textRect, DT_SINGLELINE|DT_NOPREFIX|DT_VCENTER);
1340 }
1341 }
1342 }
1343
1344
1345 #ifdef _LIGHT_STARTMENU
1346
1347 void StartMenu::ResizeToButtons()
1348 {
1349 WindowRect rect(_hwnd);
1350
1351 WindowCanvas canvas(_hwnd);
1352 FontSelection font(canvas, GetStockFont(DEFAULT_GUI_FONT));
1353
1354 int max_width = STARTMENU_WIDTH_MIN;
1355 int height = 0;
1356
1357 for(SMBtnVector::const_iterator it=_buttons.begin(); it!=_buttons.end(); ++it) {
1358 int w = GetStartMenuBtnTextWidth(canvas, it->_title, _hwnd);
1359
1360 if (w > max_width)
1361 max_width = w;
1362
1363 if (it->_id == -1)
1364 height += STARTMENU_SEP_HEIGHT;
1365 else
1366 height += STARTMENU_LINE_HEIGHT;
1367 }
1368
1369 // calculate new window size
1370 int text_width = max_width + 16/*icon*/ + 10/*placeholder*/ + 16/*arrow*/;
1371
1372 RECT rt_hgt = {rect.left, rect.bottom-_border_top-height, rect.left+_border_left+text_width, rect.bottom};
1373 AdjustWindowRectEx(&rt_hgt, GetWindowStyle(_hwnd), FALSE, GetWindowExStyle(_hwnd));
1374
1375 // ignore movement, only look at the size change
1376 rect.right = rect.left + (rt_hgt.right-rt_hgt.left);
1377 rect.top = rect.bottom - (rt_hgt.bottom-rt_hgt.top);
1378
1379 // move down if we are too high
1380 if (rect.top < 0) {
1381 int dy = -rect.top;
1382 rect.top += dy;
1383 rect.bottom += dy;
1384 }
1385
1386 // enable scroll mode for long start menus, which span more than the whole screen height
1387 int cyscreen = GetSystemMetrics(SM_CYSCREEN);
1388 int bottom_max = 0;
1389
1390 if (rect.bottom > cyscreen) {
1391 _arrow_btns = true;
1392
1393 _invisible_lines = (rect.bottom-cyscreen+(STARTMENU_LINE_HEIGHT-1))/STARTMENU_LINE_HEIGHT + 1;
1394 rect.bottom -= _invisible_lines * STARTMENU_LINE_HEIGHT;
1395
1396 bottom_max = rect.bottom;
1397
1398 if (_floating_btn)
1399 rect.bottom += 6; // lower scroll arrow
1400 else {
1401 _border_top += 6; // upper scroll arrow
1402 rect.bottom += 2*6; // upper+lower scroll arrow
1403 }
1404 }
1405
1406 MoveWindow(_hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE);
1407
1408 if (bottom_max) {
1409 POINT pt = {0, bottom_max};
1410
1411 ScreenToClient(_hwnd, &pt);
1412
1413 _bottom_max = pt.y;
1414 }
1415 }
1416
1417 #else // _LIGHT_STARTMENU
1418
1419 LRESULT StartMenuButton::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1420 {
1421 switch(nmsg) {
1422 case WM_MOUSEMOVE:
1423 // automatically set the focus to startmenu entries when moving the mouse over them
1424 if (GetFocus()!=_hwnd && !(GetWindowStyle(_hwnd)&WS_DISABLED))
1425 SetFocus(_hwnd);
1426 break;
1427
1428 case WM_SETFOCUS:
1429 PostParent(PM_STARTENTRY_FOCUSED, _hasSubmenu, (LPARAM)_hwnd);
1430 goto def;
1431
1432 case WM_CANCELMODE:
1433 // route WM_CANCELMODE to the startmenu window
1434 return SendParent(nmsg, wparam, lparam);
1435
1436 default: def:
1437 return super::WndProc(nmsg, wparam, lparam);
1438 }
1439
1440 return 0;
1441 }
1442
1443 void StartMenuButton::DrawItem(LPDRAWITEMSTRUCT dis)
1444 {
1445 TCHAR title[BUFFER_LEN];
1446
1447 GetWindowText(_hwnd, title, BUFFER_LEN);
1448
1449 DrawStartMenuButton(dis->hDC, dis->rcItem, title, _hIcon,
1450 _hasSubmenu,
1451 !(dis->itemState & ODS_DISABLED),
1452 dis->itemState&ODS_FOCUS? true: false,
1453 dis->itemState&ODS_SELECTED? true: false);
1454 }
1455
1456 #endif
1457
1458
1459 StartMenuRoot::StartMenuRoot(HWND hwnd)
1460 : super(hwnd)
1461 {
1462 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1463 if (!g_Globals._SHRestricted || !SHRestricted(REST_NOCOMMONGROUPS))
1464 #endif
1465 try {
1466 // insert directory "All Users\Start Menu"
1467 ShellDirectory cmn_startmenu(GetDesktopFolder(), SpecialFolderPath(CSIDL_COMMON_STARTMENU, _hwnd), _hwnd);
1468 _dirs.push_back(StartMenuDirectory(cmn_startmenu, false)); // don't add subfolders
1469 } catch(COMException&) {
1470 // ignore exception and don't show additional shortcuts
1471 }
1472
1473 try {
1474 // insert directory "<user name>\Start Menu"
1475
1476 ShellDirectory usr_startmenu(GetDesktopFolder(), SpecialFolderPath(CSIDL_STARTMENU, _hwnd), _hwnd);
1477 _dirs.push_back(StartMenuDirectory(usr_startmenu, false)); // don't add subfolders
1478 } catch(COMException&) {
1479 // ignore exception and don't show additional shortcuts
1480 }
1481
1482 // read size of logo bitmap
1483 BITMAP bmp_hdr;
1484 GetObject(ResBitmap(IDB_LOGOV), sizeof(BITMAP), &bmp_hdr);
1485 _logo_size.cx = bmp_hdr.bmWidth;
1486 _logo_size.cy = bmp_hdr.bmHeight;
1487
1488 _border_left = _logo_size.cx + 1;
1489 }
1490
1491
1492 HWND StartMenuRoot::Create(HWND hwndOwner)
1493 {
1494 WindowRect pos(hwndOwner);
1495
1496 RECT rect = {pos.left, pos.top-STARTMENU_LINE_HEIGHT-4, pos.left+STARTMENU_WIDTH_MIN, pos.top};
1497
1498 #ifndef _LIGHT_STARTMENU
1499 rect.top += STARTMENU_LINE_HEIGHT;
1500 #endif
1501
1502 AdjustWindowRectEx(&rect, WS_POPUP|WS_THICKFRAME|WS_CLIPCHILDREN|WS_VISIBLE, FALSE, 0);
1503
1504 return Window::Create(WINDOW_CREATOR(StartMenuRoot), 0, GetWndClasss(), TITLE_STARTMENU,
1505 WS_POPUP|WS_THICKFRAME|WS_CLIPCHILDREN,
1506 rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, hwndOwner);
1507 }
1508
1509
1510 void StartMenuRoot::TrackStartmenu()
1511 {
1512 MSG msg;
1513 HWND hwnd = _hwnd;
1514
1515 #ifdef _LIGHT_STARTMENU
1516 _selected_id = -1;
1517 #endif
1518
1519 // show previously hidden start menu
1520 ShowWindow(hwnd, SW_SHOW);
1521 SetForegroundWindow(hwnd);
1522
1523 while(IsWindow(hwnd)) {
1524 if (!GetMessage(&msg, 0, 0, 0)) {
1525 PostQuitMessage(msg.wParam);
1526 break;
1527 }
1528
1529 // Check for a mouse click on any window, which is not part of the start menu
1530 if (msg.message==WM_LBUTTONDOWN || msg.message==WM_MBUTTONDOWN || msg.message==WM_RBUTTONDOWN) {
1531 StartMenu* menu_wnd = NULL;
1532
1533 for(HWND hwnd=msg.hwnd; hwnd; hwnd=GetParent(hwnd)) {
1534 menu_wnd = WINDOW_DYNAMIC_CAST(StartMenu, hwnd);
1535
1536 if (menu_wnd)
1537 break;
1538 }
1539
1540 if (!menu_wnd) {
1541 CloseStartMenu();
1542 break;
1543 }
1544 }
1545
1546 try {
1547 if (pretranslate_msg(&msg))
1548 continue;
1549
1550 if (dispatch_dialog_msg(&msg))
1551 continue;
1552
1553 TranslateMessage(&msg);
1554
1555 try {
1556 DispatchMessage(&msg);
1557 } catch(COMException& e) {
1558 HandleException(e, _hwnd);
1559 }
1560 } catch(COMException& e) {
1561 HandleException(e, _hwnd);
1562 }
1563 }
1564 }
1565
1566
1567 LRESULT StartMenuRoot::Init(LPCREATESTRUCT pcs)
1568 {
1569 // add buttons for entries in _entries
1570 if (super::Init(pcs))
1571 return 1;
1572
1573 AddSeparator();
1574
1575
1576 #ifdef __MINGW32__
1577 HKEY hkey, hkeyAdv;
1578 DWORD value, len;
1579
1580 if (RegOpenKey(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"), &hkey))
1581 hkey = 0;
1582
1583 if (RegOpenKey(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), &hkeyAdv))
1584 hkeyAdv = 0;
1585
1586 #define IS_VALUE_ZERO(hk, name) \
1587 (!hk || (len=sizeof(value),RegQueryValueEx(hk, name, NULL, NULL, (LPBYTE)&value, &len) || !value))
1588
1589 #define IS_VALUE_NOT_ZERO(hk, name) \
1590 (!hk || (len=sizeof(value),RegQueryValueEx(hk, name, NULL, NULL, (LPBYTE)&value, &len) || value>0))
1591 #endif
1592
1593
1594 // insert hard coded start entries
1595 AddButton(ResString(IDS_PROGRAMS), ICID_APPS, true, IDC_PROGRAMS);
1596
1597 AddButton(ResString(IDS_DOCUMENTS), ICID_DOCUMENTS, true, IDC_DOCUMENTS);
1598
1599 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1600 if (!g_Globals._SHRestricted || !SHRestricted(REST_NORECENTDOCSMENU))
1601 #else
1602 if (IS_VALUE_ZERO(hkey, _T("NoRecentDocsMenu")))
1603 #endif
1604 AddButton(ResString(IDS_RECENT), ICID_DOCUMENTS, true, IDC_RECENT);
1605
1606 AddButton(ResString(IDS_FAVORITES), ICID_FAVORITES, true, IDC_FAVORITES);
1607
1608 AddButton(ResString(IDS_SETTINGS), ICID_CONFIG, true, IDC_SETTINGS);
1609
1610 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1611 if (!g_Globals._SHRestricted || !SHRestricted(REST_NOFIND))
1612 #else
1613 if (IS_VALUE_ZERO(hkey, _T("NoFind")))
1614 #endif
1615 AddButton(ResString(IDS_SEARCH), ICID_SEARCH, true, IDC_SEARCH);
1616
1617 AddButton(ResString(IDS_START_HELP), ICID_INFO, false, IDC_START_HELP);
1618
1619 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1620 if (!g_Globals._SHRestricted || !SHRestricted(REST_NORUN))
1621 #else
1622 if (IS_VALUE_ZERO(hkey, _T("NoRun")))
1623 #endif
1624 AddButton(ResString(IDS_LAUNCH), ICID_ACTION, false, IDC_LAUNCH);
1625
1626
1627 AddSeparator();
1628
1629
1630 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1631 if (!g_Globals._SHRestricted || !SHRestricted(REST_NOCLOSE))
1632 #else
1633 if (IS_VALUE_NOT_ZERO(hkeyAdv, _T("StartMenuLogoff")))
1634 #endif
1635 AddButton(ResString(IDS_LOGOFF), ICID_LOGOFF, false, IDC_LOGOFF);
1636
1637
1638 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1639 if (!g_Globals._SHRestricted || SHRestricted(REST_STARTMENULOGOFF) != 1)
1640 #else
1641 if (IS_VALUE_ZERO(hkey, _T("NoClose")))
1642 #endif
1643 AddButton(ResString(IDS_SHUTDOWN), ICID_LOGOFF, false, IDC_SHUTDOWN);
1644
1645
1646 #ifdef __MINGW32__
1647 RegCloseKey(hkeyAdv);
1648 RegCloseKey(hkey);
1649 #endif
1650
1651
1652 #ifdef _LIGHT_STARTMENU
1653 // set the window size to fit all buttons
1654 ResizeToButtons();
1655 #endif
1656
1657 return 0;
1658 }
1659
1660
1661 void StartMenuRoot::AddEntries()
1662 {
1663 super::AddEntries();
1664
1665 AddButton(ResString(IDS_EXPLORE), ICID_EXPLORER, false, IDC_EXPLORE);
1666 }
1667
1668
1669 LRESULT StartMenuRoot::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1670 {
1671 switch(nmsg) {
1672 case WM_PAINT: {
1673 PaintCanvas canvas(_hwnd);
1674 Paint(canvas);
1675 break;}
1676
1677 default:
1678 return super::WndProc(nmsg, wparam, lparam);
1679 }
1680
1681 return 0;
1682 }
1683
1684 void StartMenuRoot::Paint(PaintCanvas& canvas)
1685 {
1686 int clr_bits;
1687 {WindowCanvas dc(_hwnd); clr_bits=GetDeviceCaps(dc, BITSPIXEL);}
1688
1689 MemCanvas mem_dc;
1690 ResBitmap bmp(clr_bits<=8? clr_bits<=4? IDB_LOGOV16: IDB_LOGOV256: IDB_LOGOV);
1691 BitmapSelection sel(mem_dc, bmp);
1692
1693 ClientRect clnt(_hwnd);
1694 int h = min(_logo_size.cy, clnt.bottom);
1695
1696 RECT rect = {0, 0, _logo_size.cx, clnt.bottom-h};
1697 HBRUSH hbr = CreateSolidBrush(GetPixel(mem_dc, 0, 0));
1698 FillRect(canvas, &rect, hbr);
1699 DeleteObject(hbr);
1700
1701 PatBlt(canvas, _logo_size.cx, 0, 1, clnt.bottom, WHITENESS);
1702
1703 BitBlt(canvas, 0, clnt.bottom-h, _logo_size.cx, h, mem_dc, 0, 0, SRCCOPY);
1704
1705 super::Paint(canvas);
1706 }
1707
1708
1709 void StartMenuRoot::CloseStartMenu(int id)
1710 {
1711 if (_submenu)
1712 CloseSubmenus();
1713
1714 ShowWindow(_hwnd, SW_HIDE);
1715 }
1716
1717 void StartMenuRoot::ProcessKey(int vk)
1718 {
1719 switch(vk) {
1720 case VK_LEFT:
1721 if (_submenu)
1722 CloseOtherSubmenus();
1723 // don't close start menu root
1724 break;
1725
1726 default:
1727 super::ProcessKey(vk);
1728 }
1729 }
1730
1731
1732 int StartMenuHandler::Command(int id, int code)
1733 {
1734 switch(id) {
1735
1736 // start menu root
1737
1738 case IDC_PROGRAMS:
1739 CreateSubmenu(id, CSIDL_COMMON_PROGRAMS, CSIDL_PROGRAMS, ResString(IDS_PROGRAMS));
1740 break;
1741
1742 case IDC_EXPLORE:
1743 CloseStartMenu(id);
1744 explorer_show_frame(SW_SHOWNORMAL);
1745 break;
1746
1747 case IDC_LAUNCH:
1748 CloseStartMenu(id);
1749 ShowLaunchDialog(g_Globals._hwndDesktopBar);
1750 break;
1751
1752 case IDC_DOCUMENTS:
1753 CreateSubmenu(id, CSIDL_PERSONAL, ResString(IDS_DOCUMENTS));
1754 break;
1755
1756 case IDC_RECENT:
1757 CreateSubmenu(id, CSIDL_RECENT, ResString(IDS_RECENT), STARTMENU_CREATOR(RecentStartMenu));
1758 break;
1759
1760 case IDC_FAVORITES:
1761 CreateSubmenu(id, CSIDL_FAVORITES, ResString(IDS_FAVORITES));
1762 break;
1763
1764 case IDC_SETTINGS:
1765 CreateSubmenu(id, ResString(IDS_SETTINGS), STARTMENU_CREATOR(SettingsMenu));
1766 break;
1767
1768 case IDC_SEARCH:
1769 CreateSubmenu(id, ResString(IDS_SEARCH), STARTMENU_CREATOR(SearchMenu));
1770 break;
1771
1772 case IDC_START_HELP:
1773 CloseStartMenu(id);
1774 MessageBox(g_Globals._hwndDesktopBar, TEXT("Help not yet implemented"), ResString(IDS_TITLE), MB_OK);
1775 break;
1776
1777 case IDC_LOGOFF:
1778 /* The shell32 Dialog prompts about some system setting change. This is not what we want to display here.
1779 CloseStartMenu(id);
1780 ShowRestartDialog(g_Globals._hwndDesktopBar, EWX_LOGOFF);*/
1781 DestroyWindow(GetParent(_hwnd));
1782 break;
1783
1784 case IDC_SHUTDOWN:
1785 CloseStartMenu(id);
1786 ShowExitWindowsDialog(g_Globals._hwndDesktopBar);
1787 break;
1788
1789
1790 // settings menu
1791
1792 case ID_DESKTOPBAR_SETTINGS:
1793 CloseStartMenu(id);
1794 ExplorerPropertySheet(g_Globals._hwndDesktopBar);
1795 break;
1796
1797 case IDC_PRINTERS:
1798 CloseStartMenu(id);
1799 MainFrame::Create(SpecialFolderPath(CSIDL_PRINTERS, _hwnd), OWM_PIDL);
1800 break;
1801
1802 case IDC_CONTROL_PANEL:
1803 CloseStartMenu(id);
1804 MainFrame::Create(TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}"), 0);
1805 break;
1806
1807 case IDC_ADMIN:
1808 CloseStartMenu(id);
1809 MainFrame::Create(SpecialFolderPath(CSIDL_COMMON_ADMINTOOLS, _hwnd), OWM_PIDL);
1810 break;
1811
1812 case IDC_CONNECTIONS:
1813 CloseStartMenu(id);
1814 MainFrame::Create(SpecialFolderPath(CSIDL_CONNECTIONS, _hwnd), OWM_PIDL);
1815 break;
1816
1817
1818 // search menu
1819
1820 case IDC_SEARCH_FILES:
1821 CloseStartMenu(id);
1822 ShowSearchDialog();
1823 break;
1824
1825 case IDC_SEARCH_COMPUTER:
1826 CloseStartMenu(id);
1827 ShowSearchComputer();
1828 break;
1829
1830
1831 default:
1832 return super::Command(id, code);
1833 }
1834
1835 return 0;
1836 }
1837
1838
1839 void StartMenuHandler::ShowSearchDialog()
1840 {
1841 static DynamicFct<SHFINDFILES> SHFindFiles(TEXT("SHELL32"), 90);
1842
1843 if (SHFindFiles)
1844 (*SHFindFiles)(NULL, NULL);
1845 else
1846 MessageBox(0, TEXT("SHFindFiles() not yet implemented in SHELL32"), ResString(IDS_TITLE), MB_OK);
1847 }
1848
1849 void StartMenuHandler::ShowSearchComputer()
1850 {
1851 static DynamicFct<SHFINDCOMPUTER> SHFindComputer(TEXT("SHELL32"), 91);
1852
1853 if (SHFindComputer)
1854 (*SHFindComputer)(NULL, NULL);
1855 else
1856 MessageBox(0, TEXT("SHFindComputer() not yet implemented in SHELL32"), ResString(IDS_TITLE), MB_OK);
1857 }
1858
1859 void StartMenuHandler::ShowLaunchDialog(HWND hwndOwner)
1860 {
1861 ///@todo All text phrases should be put into the resources.
1862 static LPCSTR szTitle = "Run";
1863 static LPCSTR szText = "Type the name of a program, folder, document, or Internet resource, and Explorer will open it for you.";
1864
1865 static DynamicFct<RUNFILEDLG> RunFileDlg(TEXT("SHELL32"), 61);
1866
1867 // Show "Run..." dialog
1868 if (RunFileDlg) {
1869 #ifndef _ROS_ /* FIXME: our shell32 always expects Ansi strings */
1870 #define W_VER_NT 0
1871 if ((HIWORD(GetVersion())>>14) == W_VER_NT) {
1872 WCHAR wTitle[40], wText[256];
1873
1874 MultiByteToWideChar(CP_ACP, 0, szTitle, -1, wTitle, 40);
1875 MultiByteToWideChar(CP_ACP, 0, szText, -1, wText, 256);
1876
1877 (*RunFileDlg)(hwndOwner, 0, NULL, (LPCSTR)wTitle, (LPCSTR)wText, RFF_CALCDIRECTORY);
1878 }
1879 else
1880 #endif
1881 (*RunFileDlg)(hwndOwner, 0, NULL, szTitle, szText, RFF_CALCDIRECTORY);
1882 }
1883 }
1884
1885 void StartMenuHandler::ShowRestartDialog(HWND hwndOwner, UINT flags)
1886 {
1887 static DynamicFct<RESTARTWINDOWSDLG> RestartDlg(TEXT("SHELL32"), 59);
1888
1889 if (RestartDlg)
1890 (*RestartDlg)(hwndOwner, (LPWSTR)L"You selected <Log Off>.\n\n", flags); ///@todo ANSI string conversion if needed
1891 else
1892 MessageBox(hwndOwner, TEXT("RestartDlg() not yet implemented in SHELL32"), ResString(IDS_TITLE), MB_OK);
1893 }
1894
1895 void ShowExitWindowsDialog(HWND hwndOwner)
1896 {
1897 static DynamicFct<EXITWINDOWSDLG> ExitWindowsDlg(TEXT("SHELL32"), 60);
1898
1899 if (ExitWindowsDlg)
1900 (*ExitWindowsDlg)(hwndOwner);
1901 else
1902 MessageBox(hwndOwner, TEXT("ExitWindowsDlg() not yet implemented in SHELL32"), ResString(IDS_TITLE), MB_OK);
1903 }
1904
1905
1906 void SettingsMenu::AddEntries()
1907 {
1908 super::AddEntries();
1909
1910 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1911 if (!g_Globals._SHRestricted || !SHRestricted(REST_NOCONTROLPANEL))
1912 #endif
1913 AddButton(ResString(IDS_CONTROL_PANEL), ICID_CONFIG, false, IDC_CONTROL_PANEL);
1914
1915 AddButton(ResString(IDS_PRINTERS), ICID_PRINTER, false, IDC_PRINTERS);
1916 AddButton(ResString(IDS_CONNECTIONS), ICID_NETWORK, false, IDC_CONNECTIONS);
1917 AddButton(ResString(IDS_ADMIN), ICID_CONFIG, false, IDC_ADMIN);
1918
1919 AddButton(ResString(IDS_DESKTOPBAR_SETTINGS), ICID_CONFIG, false, ID_DESKTOPBAR_SETTINGS);
1920 }
1921
1922 void SearchMenu::AddEntries()
1923 {
1924 super::AddEntries();
1925
1926 AddButton(ResString(IDS_SEARCH_FILES), ICID_SEARCH_DOC, false, IDC_SEARCH_FILES);
1927
1928 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
1929 if (!g_Globals._SHRestricted || !SHRestricted(REST_HASFINDCOMPUTERS))
1930 #endif
1931 AddButton(ResString(IDS_SEARCH_COMPUTER), ICID_COMPUTER, false, IDC_SEARCH_COMPUTER);
1932 }
1933
1934
1935 void RecentStartMenu::AddEntries()
1936 {
1937 for(StartMenuShellDirs::iterator it=_dirs.begin(); it!=_dirs.end(); ++it) {
1938 StartMenuDirectory& smd = *it;
1939 ShellDirectory& dir = smd._dir;
1940
1941 if (!dir._scanned) {
1942 WaitCursor wait;
1943
1944 #ifdef _LAZY_ICONEXTRACT
1945 dir.smart_scan(SCAN_FILESYSTEM);
1946 #else
1947 dir.smart_scan(SCAN_EXTRACT_ICONS|SCAN_FILESYSTEM);
1948 #endif
1949 }
1950
1951 dir.sort_directory(SORT_DATE);
1952 AddShellEntries(dir, RECENT_DOCS_COUNT, smd._subfolders); ///@todo read max. count of entries from registry
1953 }
1954 }