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 "StringItem.h"
32: #include "MenuItem.h"
33: #include "Menu.h"
34: #include "..\Settings\Settings.h"
35: #include "..\Graphics\BImage.h"
36: #include "..\Toolbar\Toolbar.h"
37:
38: extern MenuCommon* pMenuCommon;
39: extern Settings *pSettings;
40: extern BImage *pBImage;
41: extern Toolbar *pToolbar;
42:
43: //===========================================================================
44:
45: StringItem::StringItem(char* pszTitleANSI, wchar_t* pszTitleUnicode, char* pszCommand, char* pszInit, bool isSelected) : MenuItem(MENUITEM_EDITSTRING, pszTitleANSI, pszTitleUnicode, NULL, NULL, isSelected)
46: {
47: m_pszTitleANSI = pszTitleANSI ? _strdup(pszTitleANSI) : _strdup("");
48: m_pszTitleUnicode = pszTitleUnicode ? _wcsdup(pszTitleUnicode) : _wcsdup(L"");
49: m_pszCommand = pszCommand ? _strdup(pszCommand) : _strdup("");
50: m_pszInit = pszInit ? _strdup(pszInit) : _strdup("");
51:
52: m_hwndEditor = NULL;
53:
54: editboxActive = false;
55: }
56:
57: StringItem::~StringItem()
58: {
59: DestroyEditWindow();
60:
61: if (m_pszTitleANSI) free(m_pszTitleANSI);
62: m_pszTitleANSI = NULL;
63: if (m_pszTitleUnicode) free(m_pszTitleUnicode);
64: m_pszTitleUnicode = NULL;
65: if (m_pszCommand) free(m_pszCommand);
66: m_pszCommand = NULL;
67: if (m_pszInit) free(m_pszInit);
68: m_pszInit = NULL;
69: }
70:
71: //===========================================================================
72:
73: void StringItem::CreateEditWindow()
74: {
75: if (!m_hwndEditor)
76: {
77: RECT workArea;
78: ZeroMemory(&workArea, sizeof(workArea));
79: SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
80:
81: int editboxWidth = (workArea.right - workArea.left) / 2;
82: int editboxHeight = 15 * pSettings->scalingFactorHiDPI; // Using a regular DPI fixed font size of 15 pixels... (Segoe UI)
83: editboxHeight += 12; // Add vertical padding to allow for a WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE sunken double border (see extended window styles below)
84: int editboxX = editboxWidth / 2;
85: int editboxY = ((workArea.bottom - workArea.top) / 2) - (editboxHeight / 2);
86:
87: m_hwndEditor = CreateWindowEx(
88: WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE, // window style
89: "EDIT", // window class
90: NULL, // window name
91: WS_POPUP | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, // window parameters
92: editboxX, // x position
93: editboxY, // y position
94: editboxWidth, // window width
95: editboxHeight, // window height
96: m_pParent->GetWindow(), // parent window
97: NULL, // no menu
98: m_pParent->hMenuInstance, // hInstance
99: NULL // no window creation data
100: );
101:
102: if (!m_hwndEditor)
103: {
104: MessageBox(0, "Error creating menu string editor window!", "BBMenu", MB_OK | MB_ICONERROR | MB_TOPMOST);
105: Log("Menu", "Error creating string editor window!");
106: m_hwndEditor = NULL;
107: }
108: else
109: {
110: SendMessage(m_hwndEditor, (UINT)WM_SETFONT, (WPARAM)pMenuCommon->m_hEditStringFont, TRUE);
111: SendMessage(m_hwndEditor, (UINT)EM_SETLIMITTEXT, (WPARAM)255, 0); // Allow maximum 255 characters in the edit control
112: SendMessage(m_hwndEditor, WM_SETTEXT, 0, (LPARAM)m_pszInit);
113: SendMessage(m_hwndEditor, EM_SETSEL, (WPARAM)0, (LPARAM)-1); // Select all text in the edit control
114:
115: // Add a subclass to the edit window to be able to respond to the Enter key (not visible to the parent WndProc) being pressed...
116: SetWindowSubclass(m_hwndEditor, EditWindowSubclassProc, 0, 0);
117:
118: ShowWindow(m_hwndEditor, SW_SHOW);
119: SetWindowPos(m_hwndEditor, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);
120: SetForegroundWindow(m_hwndEditor);
121: }
122: }
123: }
124:
125: void StringItem::DestroyEditWindow()
126: {
127: if (m_hwndEditor)
128: {
129: pSettings->menuEditboxAlreadyActive = false;
130: editboxActive = false;
131: ShowWindow(m_hwndEditor, SW_HIDE);
132: RemoveWindowSubclass(m_hwndEditor, EditWindowSubclassProc, 0);
133: DestroyWindow(m_hwndEditor);
134: m_hwndEditor = NULL;
135: }
136: }
137:
138: LRESULT CALLBACK EditWindowSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
139: {
140: if (pSettings->menuEditboxStringItemPtr != NULL)
141: {
142: if (message == WM_KEYDOWN)
143: {
144: if (wParam == VK_RETURN)
145: {
146: // Pressing the return key -> Same action as *right* clicking the "OK" button, i.e. invoke action + keep the menu open...
147: pSettings->menuEditboxStringItemPtr->Invoke(2);
148: }
149: else if (wParam == VK_ESCAPE)
150: {
151: // Pressing the escape key -> Hide the editor window *without* invoking any action, i.e. discard any edits + keep the menu open...
152: StringItem* si = (StringItem*)pSettings->menuEditboxStringItemPtr;
153: si->DestroyEditWindow();
154: si->editboxActive = false;
155: pSettings->menuEditboxStringItemPtr = NULL;
156: pSettings->menuEditboxAlreadyActive = false;
157:
158: si->m_pParent->Invalidate();
159: si->m_pParent->Validate();
160: si->m_pParent->UpdateMenuWindow();
161: }
162: }
163: }
164:
165: return DefSubclassProc(hwnd, message, wParam, lParam);
166: }
167:
168: //===========================================================================
169:
170: void StringItem::Invoke(int button)
171: {
172: if ((button == 1) && !editboxActive)
173: {
174: // Execute the broam command+parameters associated with the StringItem, without prior editing...
175: // (cf. e.g. a browser bookmark menu item scenario -> Open/execute (left click) or edit (right click) the bookmark via a single menu item)
176: pMenuCommon->Invoke(this, 1);
177: return;
178: }
179:
180: //====================
181:
182: else if (editboxActive)
183: {
184: pSettings->menuEditboxAlreadyActive = false;
185: editboxActive = false;
186: pSettings->menuEditboxStringItemPtr = NULL;
187:
188: // Fetch the newly edited string from the edit box...
189: char editedValue[255];
190: SendMessage(m_hwndEditor, WM_GETTEXT, 255, (LPARAM)&editedValue);
191: editedValue[sizeof(editedValue)-1] = '\0'; // Failsafe
192:
193: // Update the menu item title as applicable...
194:
195: if ((strlen(m_pszTitleANSI) >= 3) && !_strnicmp(&m_pszTitleANSI[strnlen_s(m_pszTitleANSI, sizeofArray(m_pszTitleANSI))-3], "...", 3))
196: {
197: // Hidden init string -> Nothing to do here... (for now at least?)
198: }
199: else // Visible init string...
200: {
201: if (strlen(m_pszInit) > 0) // -> Prior init string existed -> Replace it with the newly edited one...
202: {
203: char* ptr1 = strstr(m_pszTitleANSI, m_pszInit);
204: if (ptr1 != NULL)
205: {
206: char* ptr0 = &m_pszTitleANSI[0];
207: char* ptr2 = ptr1 + strlen(m_pszInit);
208:
209: char newTitle[MAX_LINE_LENGTH];
210: // strncpy(newTitle, m_pszTitleANSI, (int)(ptr1-ptr0));
211: strncpy_s(newTitle, sizeof(newTitle), m_pszTitleANSI, (int)(ptr1-ptr0));
212: newTitle[(int)(ptr1-ptr0)] = '\0';
213: if (strnlen_s(editedValue, sizeofArray(editedValue)) > 0) strcat_s(newTitle, sizeofArray(newTitle), editedValue);
214: strcat_s(newTitle, sizeofArray(newTitle), ptr2);
215: newTitle[sizeof(newTitle)-1] = '\0'; // Failsafe
216:
217: // if (pSettings->debugLogging) SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INFORMATION_MESSAGE, (LPARAM)newTitle);
218:
219: if (m_pszTitleANSI) free(m_pszTitleANSI);
220: m_pszTitleANSI = newTitle ? _strdup(newTitle) : _strdup("");
221:
222: wchar_t newTitleUnicode[MAX_LINE_LENGTH];
223: MultiByteToWideChar(CP_UTF8, 0, newTitle, strnlen_s(newTitle, sizeofArray(newTitle)) + 1, newTitleUnicode, MAX_LINE_LENGTH);
224: if (m_pszTitleUnicode) free(m_pszTitleUnicode);
225: m_pszTitleUnicode = newTitleUnicode ? _wcsdup(newTitleUnicode) : _wcsdup(L"");
226: }
227: }
228: else // -> No prior init string existed -> Look for the default edit string <> brackets and insert the newly edited string in between them...
229: {
230: if (strnlen_s(editedValue, sizeofArray(editedValue)) > 0)
231: {
232: char* ptr0 = &m_pszTitleANSI[0];
233: char* ptr1 = strstr(m_pszTitleANSI, "<>");
234: if (ptr1 != NULL)
235: {
236: char newTitle[MAX_LINE_LENGTH];
237: // strncpy(newTitle, m_pszTitleANSI, (int)(ptr1-ptr0)+1);
238: strncpy_s(newTitle, sizeof(newTitle), m_pszTitleANSI, (int)(ptr1-ptr0)+1);
239: newTitle[(int)(ptr1-ptr0)+1] = '\0';
240: if (strnlen_s(editedValue, sizeofArray(editedValue)) > 0) strcat_s(newTitle, sizeofArray(newTitle), editedValue);
241: strcat_s(newTitle, sizeofArray(newTitle), ptr1+1);
242: newTitle[sizeof(newTitle)-1] = '\0'; // Failsafe
243:
244: // if (pSettings->debugLogging) SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INFORMATION_MESSAGE, (LPARAM)newTitle);
245:
246: if (m_pszTitleANSI) free(m_pszTitleANSI);
247: m_pszTitleANSI = newTitle ? _strdup(newTitle) : _strdup("");
248:
249: wchar_t newTitleUnicode[MAX_LINE_LENGTH];
250: MultiByteToWideChar(CP_UTF8, 0, newTitle, strnlen_s(newTitle, sizeofArray(newTitle)) + 1, newTitleUnicode, MAX_LINE_LENGTH);
251: if (m_pszTitleUnicode) free(m_pszTitleUnicode);
252: m_pszTitleUnicode = newTitleUnicode ? _wcsdup(newTitleUnicode) : _wcsdup(L"");
253: }
254: }
255: }
256:
257: MenuItem::SetTitleANSI(m_pszTitleANSI);
258: MenuItem::SetTitleUnicode(m_pszTitleUnicode);
259: }
260:
261: // Save the newly edited string as the new menu item init string...
262: if (m_pszInit) free(m_pszInit);
263: m_pszInit = editedValue ? _strdup(editedValue) : _strdup("");
264:
265: // Set the menu item as selected if so applicable...
266: if (m_selectionGroup > GROUP_DEFAULT)
267: {
268: m_pParent->ClearSelectionGroup(m_selectionGroup);
269: m_isSelected = true;
270: }
271:
272: //====================
273:
274: // Send the broam command associated with the StringItem...
275: char broam[MAX_LINE_LENGTH];
276:
277: if (IsInString(m_pszCommand, "@BBInterface"))
278: {
279: // Add "" encapsulation to the broam parameters string to
280: // allow spaces in strings sent back to BBInterface...
281: int nLen = strnlen_s(editedValue, sizeofArray(editedValue));
282: memmove(editedValue+1, editedValue, strnlen_s(editedValue, sizeofArray(editedValue)));
283: editedValue[0] = editedValue[nLen+1] = '\"';
284: editedValue[nLen+2] = '\0';
285:
286: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INFORMATION_MESSAGE, (LPARAM)"StringItem::Invoke() broadcasting back...");
287: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INDENTED_MESSAGE, (LPARAM)m_pszCommand);
288: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INDENTED_MESSAGE, (LPARAM)editedValue);
289: }
290:
291: sprintf_s(broam, sizeofArray(broam), "%s %s", m_pszCommand, editedValue);
292: // SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INDENTED_MESSAGE, (LPARAM)broam);
293: SendMessage(GetBBWnd(), BB_BROADCAST, 0, (LPARAM)broam);
294:
295: //====================
296:
297: // Hide the editor window...
298: DestroyEditWindow();
299: SetWindowPos(m_pParent->GetWindow(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
300:
301: // Update the menu...
302: m_pParent->Invalidate();
303: m_pParent->Validate();
304: m_pParent->UpdateMenuWindow();
305: }
306:
307: //====================
308:
309: else
310: {
311: // Allow only a single menu item editbox to be active at a time...
312: if (!editboxActive && !pSettings->menuEditboxAlreadyActive && (pSettings->menuEditboxStringItemPtr == NULL))
313: {
314: // Show the editor window and "OK?" button...
315: pSettings->menuEditboxAlreadyActive = true;
316: editboxActive = true;
317: pSettings->menuEditboxStringItemPtr = this;
318: SetWindowPos(m_pParent->GetWindow(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
319: CreateEditWindow();
320: }
321: else if (pSettings->menuEditboxStringItemPtr != NULL)
322: {
323: // Hide the editor window belonging to another menu item...
324: StringItem* si = (StringItem*)pSettings->menuEditboxStringItemPtr;
325: si->DestroyEditWindow();
326: si->editboxActive = false;
327: pSettings->menuEditboxStringItemPtr = NULL;
328: pSettings->menuEditboxAlreadyActive = false;
329: SetWindowPos(m_pParent->GetWindow(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
330: }
331:
332: // Update the menu...
333: m_pParent->Invalidate();
334: m_pParent->Validate();
335: m_pParent->UpdateMenuWindow();
336: }
337: }
338:
339: //===========================================================================
340:
341: bool StringItem::Active(bool bActivate)
342: {
343: if (editboxActive) return false;
344: else return MenuItem::Active(bActivate);
345: }
346:
347: //===========================================================================
348: