1: /*
2: ============================================================================
3: xoblite™ -> an advanced "extended shell" for Microsoft® Windows® 11
4: Copyright © 2002-2026 Karl Henrik Henriksson [qwilk/@xoblite]
5: Copyright © 2001-2004 The Blackbox for Windows Development Team
6: http://xoblite.net/ + https://github.com/xoblite/
7: ============================================================================
8:
9: Blackbox for Windows is free software, released under the
10: GNU General Public License (GPL version 2 or later), with an extension
11: that allows linking of proprietary modules under a controlled interface.
12: What this means is that plugins etc. are allowed to be released
13: under any license the author wishes. Please note, however, that the
14: original Blackbox gradient math code used in Blackbox for Windows
15: is available under the BSD license.
16:
17: http://www.fsf.org/licenses/gpl.html
18: http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
19: http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
20:
21: This program is distributed in the hope that it will be useful,
22: but WITHOUT ANY WARRANTY; without even the implied warranty of
23: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24: GNU General Public License for more details.
25:
26: For additional license information, please read the included license.html.
27:
28: ============================================================================
29: */
30:
31: #include "MenuItem.h"
32: #include "MenuCommon.h"
33: #include "PreviewItem.h"
34: #include "..\Settings\Settings.h"
35:
36: extern MenuCommon *pMenuCommon;
37: extern PreviewItem* pPreviewItem;
38: extern Settings *pSettings;
39:
40: //===========================================================================
41:
42: MenuItem::MenuItem(int type, char* pszTitleANSI, wchar_t* pszTitleUnicode, char* pszCommand, char* pszArgument, bool isSelected)
43: {
44: itemType = type;
45:
46: if (pszTitleANSI == NULL) m_pszTitleANSI = _strdup("");
47: else m_pszTitleANSI = _strdup(pszTitleANSI);
48:
49: if (pszTitleUnicode == NULL)
50: {
51: if (!pSettings->disableUnicodeParsing)
52: {
53: wchar_t itemTitleUnicode[MAX_LINE_LENGTH];
54: MultiByteToWideChar(CP_UTF8, 0, pszTitleANSI, strlen(pszTitleANSI)+1, itemTitleUnicode, MAX_LINE_LENGTH);
55: m_pszTitleUnicode = _wcsdup(itemTitleUnicode);
56: }
57: else m_pszTitleUnicode = _wcsdup(L"");
58: }
59: else m_pszTitleUnicode = _wcsdup(pszTitleUnicode);
60:
61: //====================
62:
63: m_pParent = NULL;
64: m_nWidth = 0;
65: m_nHeight = 0;
66:
67: m_bActive = false;
68: m_nTimerId = 0;
69:
70: m_selectionGroup = 0; // Default -> Not part of any selection group (i.e. a group of mutually exclusive boolean items)
71:
72: SetSortPriority(PRIORITY_NORMAL); // Default sort priority unless defined
73:
74: //====================
75:
76: if (type == MENUITEM_COMMAND || type == MENUITEM_BOOLEAN || type == MENUITEM_EDITINT)
77: {
78: // Creates a command-type menu item object (being either a regular, a boolean or an integer editing item)
79: // pszCommand -> The command associated with this menu item
80: // pszArgument -> Optional argument to pszCommand
81: // pszTitle -> The text to be drawn on the menu item
82:
83: m_pszCommand = pszCommand ? _strdup(pszCommand) : _strdup("");
84: m_pszArgument = pszArgument ? _strdup(pszArgument) : _strdup("");
85: m_pszOriginalTitleANSI = pszTitleANSI ? _strdup(pszTitleANSI) : _strdup("");
86: m_pszOriginalTitleUnicode = pszTitleUnicode ? _wcsdup(pszTitleUnicode) : _wcsdup(L"");
87: m_isSelected = isSelected;
88:
89: if (type == MENUITEM_EDITINT) // Used by MakeMenuItemInt...
90: {
91: char command[MAX_LINE_LENGTH], value[10], min[10], max[10];
92: LPSTR tokens[3];
93: tokens[0] = command;
94: tokens[1] = value;
95: tokens[2] = min;
96: command[0] = value[0] = min[0] = max[0] = '\0';
97: BBTokenize (m_pszCommand, tokens, 3, max);
98:
99: m_pszCommand = command ? _strdup(command) : _strdup("");
100:
101: m_currentValue = atoi(value);
102: m_minValue = atoi(min);
103: m_maxValue = atoi(max);
104:
105: char itemstringANSI[MAX_LINE_LENGTH];
106: sprintf_s(itemstringANSI, sizeofArray(itemstringANSI), "%s <%d>", m_pszOriginalTitleANSI, m_currentValue);
107: SetTitleANSI(itemstringANSI);
108:
109: wchar_t itemStringUnicode[MAX_LINE_LENGTH];
110: swprintf(itemStringUnicode, sizeof(itemStringUnicode), L"%s <%d>", m_pszOriginalTitleUnicode, m_currentValue);
111: SetTitleUnicode(itemStringUnicode);
112: }
113: }
114:
115: //====================
116:
117: else
118: {
119: m_pszCommand = NULL;
120: m_pszArgument = NULL;
121: m_pszOriginalTitleANSI = NULL;
122: m_pszOriginalTitleUnicode = NULL;
123: // m_isSelected = false;
124: m_isSelected = isSelected;
125:
126: m_currentValue = m_minValue = m_maxValue = 0;
127: }
128:
129: //====================
130:
131: // Set a higher sort priority for [separator] items to make
132: // sort before any regular menu items (i.e. between folders
133: // regular menu items)
134: // if (itemType == MENUITEM_SEPARATOR) SetSortPriority(PRIORITY_SEPARATOR);
135: // else if (itemType == MENUITEM_HEADER) SetSortPriority(PRIORITY_HEADER);
136: }
137:
138: //===========================================================================
139:
140: MenuItem::~MenuItem()
141: {
142: ItemKillTimer();
143:
144: if (m_pszTitleANSI) free(m_pszTitleANSI);
145: m_pszTitleANSI = NULL;
146: if (m_pszTitleUnicode) free(m_pszTitleUnicode);
147: m_pszTitleUnicode = NULL;
148:
149: if (m_pszOriginalTitleANSI) free(m_pszOriginalTitleANSI);
150: m_pszOriginalTitleANSI = NULL;
151: if (m_pszOriginalTitleUnicode) free(m_pszOriginalTitleUnicode);
152: m_pszOriginalTitleUnicode = NULL;
153:
154: if (m_pszCommand) free(m_pszCommand);
155: m_pszCommand = NULL;
156: if (m_pszArgument) free(m_pszArgument);
157: m_pszArgument = NULL;
158: }
159:
160: //===========================================================================
161:
162: LRESULT MenuItem::NcHitTest(int x, int y)
163: {
164: if (itemType == MENUITEM_HEADER || itemType == MENUITEM_FOOTER)
165: {
166: if (IsOver(x, y) && (!m_pParent->GetParent() || m_pParent->IsPinned())) return HTCAPTION;
167: }
168:
169: return HTCLIENT;
170: }
171:
172: //===========================================================================
173:
174: int MenuItem::IsOver(int x, int y)
175: {
176: if ((x >= 0) && (x <= GetWidth()) && (y >= m_nTop) && (y <= m_nTop + GetHeight()))
177: return true;
178: else
179: return false;
180: }
181:
182: //===========================================================================
183:
184: void MenuItem::Mouse(UINT nMsg, POINT pt)
185: {
186: // Save the mouse position...
187: // (this may be used by MenuCommon to detect where on the
188: // item you clicked, e.g. for integer editing items)
189: m_mousePos.x = pt.x;
190: m_mousePos.y = pt.y;
191:
192: //====================
193:
194: RECT r;
195: GetItemRect(&r);
196:
197: if (PtInRect(&r, pt))
198: {
199: m_nLastMouseButton = nMsg;
200:
201: switch (nMsg)
202: {
203: case WM_LBUTTONDOWN:
204: case WM_RBUTTONDOWN:
205: {
206: if (itemType == MENUITEM_EDITINT) ItemSetTimer();
207: break;
208: }
209:
210: case WM_LBUTTONUP:
211: case WM_NCLBUTTONUP:
212: {
213: ItemKillTimer();
214: Invoke(1);
215: break;
216: }
217:
218: case WM_RBUTTONUP:
219: case WM_NCRBUTTONUP:
220: {
221: ItemKillTimer();
222: // if ((itemType == MENUITEM_HEADER) && (GetAsyncKeyState(VK_MENU) & 0x8000)) m_pParent->TogglePinned();
223: if ((itemType == MENUITEM_HEADER || itemType == MENUITEM_FOOTER) && (GetAsyncKeyState(VK_MENU) & 0x8000)) m_pParent->TogglePinned();
224: else Invoke(2);
225: break;
226: }
227:
228: case WM_MBUTTONUP:
229: case WM_NCMBUTTONUP:
230: case WM_LBUTTONDBLCLK:
231: case WM_NCLBUTTONDBLCLK:
232: {
233: // Mid click or left doubleclick on menu title or grip
234: // -> Toggle pinned state for the menu...
235: // if (itemType == MENUITEM_HEADER) m_pParent->TogglePinned();
236: if (itemType == MENUITEM_HEADER || itemType == MENUITEM_FOOTER) m_pParent->TogglePinned();
237: else Invoke(3);
238: break;
239: }
240:
241: case WM_MOUSEMOVE:
242: case WM_NCMOUSEMOVE:
243: {
244: Active(true);
245: break;
246: }
247:
248: default:
249: break;
250: }
251: }
252: else Active(false);
253: }
254:
255: //===========================================================================
256:
257: bool MenuItem::Active(bool bActive)
258: {
259: if (itemType != MENUITEM_COMMAND && bActive) pPreviewItem->Hide();
260:
261: if (itemType == MENUITEM_HEADER || itemType == MENUITEM_SEPARATOR || itemType == MENUITEM_NOP
262: || itemType == MENUITEM_MARGINPAD || itemType == MENUITEM_FOOTER)
263: {
264: return false;
265: }
266:
267: //====================
268:
269: // Has the menu item active state changed?
270: if (m_bActive != bActive)
271: {
272: if (bActive)
273: {
274: // We only redraw the window when a new item becomes *active*...
275: // (i.e. to avoid double redraws as active state moves from one item to another)
276: pMenuCommon->FindActiveAndDeactivate(m_pParent, false);
277: m_bActive = true;
278: m_pParent->UpdateMenuWindow();
279:
280: // Should we also display an image preview for the new active item?
281: if (bActive && (itemType == MENUITEM_COMMAND) && !_stricmp(m_pszCommand, "[exec]"))
282: {
283: char path[MAX_PATH];
284: strcpy_s(path, sizeofArray(path), m_pszArgument);
285: if (strchr(path, '\"')) StrRemoveEncap(path);
286: if (strchr(path, '$')) ReplaceShellFolders(path);
287: if (strchr(path, '%')) ReplaceEnvVars(path);
288: int pointer = strnlen_s(path, sizeofArray(path)) - 4;
289:
290: if ((path[1] == ':') && (pointer > 0))
291: {
292: if (!_stricmp(&path[pointer], ".jpg") || !_stricmp(&path[pointer], ".png") || !_stricmp(&path[pointer], ".bmp") || !_stricmp(&path[pointer], "jpeg"))
293: {
294: int screenWidth = GetSystemMetrics(SM_CXSCREEN);
295: int previewItemWidth = pPreviewItem->GetWidth();
296:
297: RECT parentRect;
298: GetWindowRect(m_pParent->GetWindow(), &parentRect);
299:
300: int gap = 2 * pSettings->scalingFactorHiDPI;
301:
302: int x = parentRect.right + gap;
303: int y = parentRect.top + m_nTop + (m_nHeight / 2);
304:
305: // If there isn't enough space for the preview item on the right hand
306: // side of the parent menu, we move it to the left hand side instead...
307: if ((x + previewItemWidth) > screenWidth) x = parentRect.left - gap - previewItemWidth;
308:
309: // Show the preview item after a short delay...
310: // (nb. we do this to get smoother fast scrolling through parent menus,
311: // i.e. no loading of preview images on every single entry while scrolling)
312: // + Note: If width and height are both set to 0 like here, PreviewItem will
313: // auto-resize itself to match the aspect ratio of the to-be-previewed image.
314: pPreviewItem->Show(x, y, 0, 0, path, 200);
315: }
316: else pPreviewItem->Hide();
317: }
318: else pPreviewItem->Hide();
319: }
320: }
321: else m_bActive = false;
322: }
323:
324: //====================
325:
326: if (!m_bActive) ItemKillTimer();
327:
328: //====================
329:
330: return m_bActive;
331: }
332:
333: //===========================================================================
334:
335: void MenuItem::ItemSetTimer()
336: {
337: if (m_nTimerId == 0)
338: {
339: // Wait 500 milliseconds until we start changing the value...
340: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_WARNING_MESSAGE, (LPARAM)"Setting timer...");
341: m_nTimerId = (UINT)this;
342: SetTimer(GetWindow(), m_nTimerId, 500, NULL);
343: }
344: }
345:
346: //====================
347:
348: void MenuItem::Timer(int nTimer)
349: {
350: // Execute timer messages...
351: if ((UINT)nTimer == m_nTimerId)
352: {
353: // Update value and reset timer to 100 milliseconds...
354: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_WARNING_MESSAGE, (LPARAM)this->m_pszTitle);
355: KillTimer(GetWindow(), m_nTimerId);
356: if (m_nLastMouseButton == WM_RBUTTONDOWN) pMenuCommon->Invoke(this, 2);
357: else pMenuCommon->Invoke(this, 1);
358: SetTimer(GetWindow(), m_nTimerId, 100, NULL);
359: }
360: }
361:
362: //====================
363:
364: void MenuItem::ItemKillTimer()
365: {
366: if (m_nTimerId != 0)
367: {
368: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_WARNING_MESSAGE, (LPARAM)"Killing timer...");
369: KillTimer(GetWindow(), m_nTimerId);
370: m_nTimerId = 0;
371: }
372: }
373:
374: //===========================================================================
375:
376: void MenuItem::Invoke(int button)
377: {
378: if ((itemType == MENUITEM_HEADER) && (button == 2))
379: {
380: // if (!m_pParent->IsPinned()) pMenuCommon->Hide();
381: // else if (m_pParent->m_pszFolderPath != NULL)
382: if (m_pParent->m_pszFolderPath != NULL)
383: {
384: // ##### TODO: Move to MenuCommon along with similar code in SpecialFolder.cpp #####
385: char folder[MAX_LINE_LENGTH], arg[MAX_LINE_LENGTH];
386: strcpy_s(folder, sizeofArray(folder), m_pParent->m_pszFolderPath);
387:
388: if (strchr(folder, '|'))
389: {
390: // If this is a merged [path] item we use
391: // the first path when right clicking on it...
392: Tokenize(folder, arg, "|");
393: strcpy_s(folder, sizeofArray(folder), arg); // Note: Just using arg as a temp variable here...
394: }
395:
396: if (folder[0] == '\"') StrRemoveEncap(folder);
397: strcpy_s(arg, sizeofArray(arg), "/e,");
398: strcat_s(arg, sizeofArray(arg), folder);
399:
400: BBExecute(GetDesktopWindow(), NULL, "explorer.exe", arg, folder, SW_SHOWNORMAL, false);
401: PlaySoundFX(SFX_MENU_CLICK);
402: }
403: }
404: else if (itemType == MENUITEM_COMMAND || itemType == MENUITEM_BOOLEAN || itemType == MENUITEM_EDITINT) pMenuCommon->Invoke(this, button);
405: }
406:
407: //===========================================================================
408:
409: void MenuItem::GetItemRect(RECT* r)
410: {
411: r->left = m_nLeft;
412: r->top = m_nTop;
413: r->right = m_nLeft + m_nWidth;
414: r->bottom = m_nTop + m_nHeight;
415: }
416:
417: //====================
418:
419: void MenuItem::GetTitleRect(RECT* r)
420: {
421: if (itemType == MENUITEM_HEADER || itemType == MENUITEM_FOOTER) // Header and grip items
422: {
423: // r->left = m_nLeft + pSettings->MenuTitle->borderWidth + pSettings->bevelWidth + 2;
424: r->left = m_nLeft + pSettings->MenuTitle->borderWidth + 3;
425: r->top = m_nTop;
426: // r->right = m_nLeft + m_nWidth - pSettings->MenuTitle->borderWidth - pSettings->bevelWidth - 2;
427: r->right = m_nLeft + m_nWidth - pSettings->MenuTitle->borderWidth - 3;
428: r->bottom = m_nTop + m_nHeight;
429: if (!_stricmp(pSettings->MenuTitle->Font, "nu")) r->bottom--;
430: }
431: else // Normal items
432: {
433: r->left = m_nLeft + pMenuCommon->m_nLeftIndent;
434: r->top = m_nTop;
435: r->right = m_nLeft + m_nWidth - pMenuCommon->m_nRightIndent;
436: r->bottom = m_nTop + m_nHeight;
437: }
438: }
439:
440: //===========================================================================
441:
442: UINT MenuItem::GetDrawTextFormat()
443: {
444: if (itemType == MENUITEM_HEADER) // Header items
445: return pSettings->MenuTitle->Justify | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP;
446: if (itemType == MENUITEM_FOOTER) // Grip items
447: return pSettings->MenuGrip->Justify | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP;
448: else // Normal items
449: return pSettings->MenuFrame->Justify | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP;
450: }
451:
452: //===========================================================================
453:
454: void MenuItem::SetTitleANSI(char* pszTitleANSI)
455: {
456: if (m_pszTitleANSI) free(m_pszTitleANSI);
457: m_pszTitleANSI = pszTitleANSI ? _strdup(pszTitleANSI) : _strdup("");;
458: }
459:
460: void MenuItem::SetTitleUnicode(wchar_t* pszTitleUnicode)
461: {
462: if (m_pszTitleUnicode) free(m_pszTitleUnicode);
463: m_pszTitleUnicode = pszTitleUnicode ? _wcsdup(pszTitleUnicode) : _wcsdup(L"");;
464: }
465:
466: //===========================================================================
467:
468: void MenuItem::Attached(Menu* pMenu)
469: {
470: m_pParent = pMenu;
471: }
472:
473: //===========================================================================
474:
475: HWND MenuItem::GetWindow()
476: {
477: if (m_pParent) return m_pParent->GetWindow();
478: else return NULL;
479: }
480:
481: //===========================================================================
482:
483: LRESULT MenuItem::Command(WPARAM wParam, LPARAM lParam)
484: {
485: return 0;
486: }
487:
488: //===========================================================================
489: // Functions: Compare
490: // Purpose: Compares the title strings of menu items,
491: // and returns true if m1 should be sorted before m2
492: //===========================================================================
493:
494: bool MenuItem::Compare(MenuItem* m1, MenuItem* m2)
495: {
496: // Menu item priorities:
497: // MENUITEM_HEADER -> 255
498: // MENUITEM_MARGINPAD (top) -> 254
499: // MENUITEM_FOLDER -> 4
500: // MENUITEM_SEPARATOR -> 3
501: // === Regular items === -> 2
502: // MENUITEM_MARGINPAD (bottom) -> 1
503: // MENUITEM_FOOTER -> 0
504:
505: int m1_sort = m1->GetSortPriority();
506: int m2_sort = m2->GetSortPriority();
507:
508: if (m1_sort == m2_sort)
509: {
510: if (m1->m_pszTitleUnicode != NULL)
511: {
512: if (m2->m_pszTitleUnicode != NULL) return _wcsicmp(m1->m_pszTitleUnicode, m2->m_pszTitleUnicode) < 0; // -> Both m1 and m2 are Unicode items, do a regular string compare...
513: else return true; // -> m1 being a Unicode item is sorted before m2 being an ANSI item...
514: }
515: else if (m2->m_pszTitleUnicode != NULL) return false;
516: else return _stricmp(m1->GetSortString(), m2->GetSortString()) < 0; // -> Both m1 and m2 are ANSI items, do a regular string compare...
517: }
518: else return m1_sort > m2_sort;
519: }
520:
521: //===========================================================================
522: // Functions: ToggleSelected
523: // Purpose: ...
524: //===========================================================================
525:
526: void MenuItem::ToggleSelected()
527: {
528: if (m_isSelected) m_isSelected = false;
529: else m_isSelected = true;
530: }
531:
532: //===========================================================================
533: // Functions: AddToSelectionGroup
534: // Purpose: ...
535: //===========================================================================
536:
537: void MenuItem::AddToSelectionGroup(int group)
538: {
539: m_selectionGroup = group;
540: }
541:
542: //===========================================================================
543: