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: