From e1c4b80991b22379a21d26ac1e92e9179eb3aa73 Mon Sep 17 00:00:00 2001 From: eccles Date: Sun, 4 Jan 2004 14:06:39 +0000 Subject: [PATCH] DLL version 2.4 (1/4/2004) * Initial focus is set in "initDialog" making it possible to override it from NSIS prior to calling "show" - When initial focus is to a Text field InstallOptions now follows standard Windows behaviour by having the text selected - Label and other static fields no longer have State= written to the INI file when leaving the dialog - NOTIFY flag can now be used with Link fields (State should be omitted in this case) - Likewise, State can now be used with Button fields (behaves the same as with Link fields) - NOTIFY flag can also now be used with ListBox and DropList fields to have NSIS notified when the selection changes - Meaning of RIGHT flag is now reversed in right-to-left language mode - HSCROLL and VSCROLL flags are no longer restricted to Text fields - Various Link field fixes - Text box colour bug fix git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@3350 212acab6-be3b-0410-9dea-997c60f758d6 --- Contrib/InstallOptions/Changelog.txt | 12 + Contrib/InstallOptions/InstallerOptions.cpp | 506 ++++++++++---------- Contrib/InstallOptions/Readme.html | 64 ++- Contrib/InstallOptions/testlink.ini | 10 +- Contrib/InstallOptions/testnotify.ini | 29 +- Contrib/InstallOptions/testnotify.nsi | 24 +- Plugins/InstallOptions.dll | Bin 12288 -> 12288 bytes 7 files changed, 372 insertions(+), 273 deletions(-) diff --git a/Contrib/InstallOptions/Changelog.txt b/Contrib/InstallOptions/Changelog.txt index 79c20011..ed8688aa 100644 --- a/Contrib/InstallOptions/Changelog.txt +++ b/Contrib/InstallOptions/Changelog.txt @@ -1,8 +1,20 @@ +DLL version 2.4 (1/4/2004) +* Initial focus is set in "initDialog" making it possible to override it from NSIS prior to calling "show" +* When initial focus is to a Text field InstallOptions now follows standard Windows behaviour by having the text selected +* Label and other static fields no longer have State= written to the INI file when leaving the dialog +* NOTIFY flag can now be used with Link fields (State should be omitted in this case) +* Likewise, State can now be used with Button fields (behaves the same as with Link fields) +* NOTIFY flag can also now be used with ListBox and DropList fields to have NSIS notified when the selection changes +* Meaning of RIGHT flag is now reversed in right-to-left language mode +* HSCROLL and VSCROLL flags are no longer restricted to Text fields +* Various Link field fixes + DLL version 2.3 (12/4/2003) * Added new control type "Button" * Added new flag "NOTIFY" * Added new flag "NOWORDWRAP" for multi-line text boxes * Reduced size down to 12K +* Better RTL support DLL version 2.2 (6/10/2003) * Added New control type LINK diff --git a/Contrib/InstallOptions/InstallerOptions.cpp b/Contrib/InstallOptions/InstallerOptions.cpp index 7669cd00..32f712e7 100644 --- a/Contrib/InstallOptions/InstallerOptions.cpp +++ b/Contrib/InstallOptions/InstallerOptions.cpp @@ -18,6 +18,15 @@ #include "../exdll/exdll.h" #undef popstring +// Use for functions only called from one place to possibly reduce some code +// size. Allows the source code to remain readable by leaving the function +// intact. +#ifdef _MSC_VER +#define INLINE __forceinline +#else +#define INLINE inline +#endif + void *WINAPI MALLOC(int len) { return (void*)GlobalAlloc(GPTR,len); } void WINAPI FREE(void *d) { if (d) GlobalFree((HGLOBAL)d); } @@ -62,57 +71,37 @@ char *WINAPI STRDUP(const char *c) //--------------------------------------------------------------------- // settings -// crashes on windows 98 - #define IO_ENABLE_LINK #define IO_ENABLE_LINK //#define IO_LINK_UNDERLINED // Uncomment to show links text underlined //--------------------------------------------------------------------- -// general flags -#define FLAG_RIGHT 0x00000001 +// Flags -// OFN_OVERWRITEPROMPT 0x00000002 -// OFN_HIDEREADONLY 0x00000004 - -#define FLAG_DISABLED 0x00000008 -#define FLAG_GROUP 0x00000010 -#define FLAG_NOTABSTOP 0x00000020 - -// text box flags -#define FLAG_PASSWORD 0x00000040 -#define FLAG_ONLYNUMBERS 0x00000080 -#define FLAG_MULTILINE 0x00000100 - -// listbox flags -#define FLAG_MULTISELECT 0x00000200 -#define FLAG_EXTENDEDSEL 0x00000400 - -// OFN_PATHMUSTEXIST 0x00000800 -// OFN_FILEMUSTEXIST 0x00001000 -// OFN_CREATEPROMPT 0x00002000 - -// combobox flags -#define FLAG_DROPLIST 0x00004000 - -// bitmap flags -#define FLAG_RESIZETOFIT 0x00008000 - -// general flags -#define FLAG_NOTIFY 0x00010000 // Notify NSIS script when control is "activated" (exact meaning depends on the type of control) - -// browse flags -#define FLAG_SAVEAS 0x00020000 // Show "Save As" instead of "Open" for FileRequest field - -// text box flags -#define FLAG_NOWORDWRAP 0x00040000 // Disable word-wrap in multi-line text boxes - -// OFN_EXPLORER 0x00080000 - -// more text box flags -#define FLAG_WANTRETURN 0x00100000 -#define FLAG_VSCROLL 0x00200000 -#define FLAG_HSCROLL 0x00400000 -#define FLAG_READONLY 0x00800000 +// LBS_NOTIFY 0x00000001 // LISTBOX/CHECKBOX/RADIOBUTTON/BUTTON/LINK - Notify NSIS script when control is "activated" (exact meaning depends on the type of control) +// OFN_OVERWRITEPROMPT 0x00000002 // FILEREQUEST +// OFN_HIDEREADONLY 0x00000004 // FILEREQUEST +// LBS_MULTIPLESEL 0x00000008 // LISTBOX +#define FLAG_READONLY 0x00000010 // TEXT/FILEREQUEST/DIRREQUEST +// BS_LEFTTEXT 0x00000020 // CHECKBOX/RADIOBUTTON +#define FLAG_PASSWORD 0x00000040 // TEXT/FILEREQUEST/DIRREQUEST +#define FLAG_ONLYNUMBERS 0x00000080 // TEXT/FILEREQUEST/DIRREQUEST +#define FLAG_MULTILINE 0x00000100 // TEXT/FILEREQUEST/DIRREQUEST +#define FLAG_NOWORDWRAP 0x00000200 // TEXT/FILEREQUEST/DIRREQUEST - Disable word-wrap in multi-line text boxes +#define FLAG_WANTRETURN 0x00000400 // TEXT/FILEREQUEST/DIRREQUEST +// LBS_EXTENDEDSEL 0x00000800 // LISTBOX +// OFN_PATHMUSTEXIST 0x00000800 // FILEREQUEST +// OFN_FILEMUSTEXIST 0x00001000 // FILEREQUEST +// OFN_CREATEPROMPT 0x00002000 // FILEREQUEST +#define FLAG_DROPLIST 0x00004000 // COMBOBOX +#define FLAG_RESIZETOFIT 0x00008000 // BITMAP +// WS_TABSTOP 0x00010000 // *ALL* +// WS_GROUP 0x00020000 // *ALL* +#define FLAG_SAVEAS 0x00040000 // FILEREQUEST - Show "Save As" instead of "Open" for FileRequest field +// OFN_EXPLORER 0x00080000 // FILEREQUEST +// WS_HSCROLL 0x00100000 // *ALL* +// WS_VSCROLL 0x00200000 // *ALL* +// WS_DISABLED 0x08000000 // *ALL* struct TableEntry { char *pszName; @@ -150,7 +139,7 @@ struct FieldType { // initial buffer size. buffers will grow as required. // use a value larger than MAX_PATH to prevent need for excessive growing. -#define MAX_BUFFER_LENGTH (300) +#define BUFFER_SIZE 8192 // 8kb of mem is max char count in multiedit char szBrowseButtonCaption[] = "..."; @@ -159,7 +148,6 @@ HWND hMainWindow = NULL; HWND hCancelButton = NULL; HWND hNextButton = NULL; HWND hBackButton = NULL; -HWND hInitialFocus = NULL; HINSTANCE m_hInstance = NULL; @@ -197,20 +185,28 @@ LRESULT WINAPI mySendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) return SendMessage(hWnd, Msg, wParam, lParam); } +void WINAPI mySetFocus(HWND hWnd) +{ + mySendMessage(hMainWindow, WM_NEXTDLGCTL, (WPARAM)hWnd, TRUE); +} + +void WINAPI mySetWindowText(HWND hWnd, LPCTSTR pszText) +{ + if (pszText) + SetWindowText(hWnd, pszText); +} int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) { - static TCHAR szDir[MAX_PATH]; + static TCHAR szDir[MAX_PATH]; - if (uMsg == BFFM_INITIALIZED) { - if (GetWindowText(pFields[(int)pData].hwnd, szDir, MAX_PATH) > 0) { - mySendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir); - } - } - return 0; + if (uMsg == BFFM_INITIALIZED && + GetWindowText(pFields[(int)pData].hwnd, szDir, MAX_PATH) > 0) + mySendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)szDir); + return 0; } -bool WINAPI ValidateFields() { +bool INLINE ValidateFields() { int nIdx; int nLength; @@ -228,7 +224,7 @@ bool WINAPI ValidateFields() { if (pField->pszValidateText) { MessageBox(hConfigWindow, pField->pszValidateText, NULL, MB_OK|MB_ICONWARNING); } - SetFocus(pField->hwnd); + mySetFocus(pField->hwnd); return false; } @@ -239,110 +235,111 @@ bool WINAPI ValidateFields() { bool WINAPI SaveSettings(void) { static char szField[25]; - int nIdx; - int nBufLen = MAX_BUFFER_LENGTH; + int nBufLen = BUFFER_SIZE; char *pszBuffer = (char*)MALLOC(nBufLen); if (!pszBuffer) return false; - int CurrField = 1; - for (nIdx = 0; nIdx < nNumFields; nIdx++) { + int nIdx; + int CurrField; + for (nIdx = 0, CurrField = 1; nIdx < nNumFields; nIdx++, CurrField++) { FieldType *pField = pFields + nIdx; HWND hwnd = pField->hwnd; switch (pField->nType) { case FIELD_BROWSEBUTTON: if (g_NotifyField > CurrField) --g_NotifyField; + --CurrField; + default: continue; - case FIELD_INVALID: - case FIELD_LABEL: - case FIELD_BUTTON: - *pszBuffer=0; - break; + case FIELD_CHECKBOX: case FIELD_RADIOBUTTON: wsprintf(pszBuffer, "%d", !!mySendMessage(hwnd, BM_GETCHECK, 0, 0)); break; + case FIELD_LISTBOX: - { - // Ok, this one requires a bit of work. - // First, we allocate a buffer long enough to hold every item. - // Then, we loop through every item and if it's selected we add it to our buffer. - // If there is already an item in the list, then we prepend a | character before the new item. - // We could simplify for single-select boxes, but using one piece of code saves some space. - int nLength = lstrlen(pField->pszListItems) + 10; - if (nLength > nBufLen) { - FREE(pszBuffer); - nBufLen = nLength; - pszBuffer = (char*)MALLOC(nBufLen); - if (!pszBuffer) return false; - } - char *pszItem = (char*)MALLOC(nBufLen); - if (!pszItem) return false; + { + // Ok, this one requires a bit of work. + // First, we allocate a buffer long enough to hold every item. + // Then, we loop through every item and if it's selected we add it to our buffer. + // If there is already an item in the list, then we prepend a | character before the new item. + // We could simplify for single-select boxes, but using one piece of code saves some space. + int nLength = lstrlen(pField->pszListItems) + 10; + if (nLength > nBufLen) { + FREE(pszBuffer); + nBufLen = nLength; + pszBuffer = (char*)MALLOC(nBufLen); + if (!pszBuffer) return false; + } + char *pszItem = (char*)MALLOC(nBufLen); + if (!pszItem) return false; - *pszBuffer = '\0'; - int nNumItems = mySendMessage(hwnd, LB_GETCOUNT, 0, 0); - for (int nIdx2 = 0; nIdx2 < nNumItems; nIdx2++) { - if (mySendMessage(hwnd, LB_GETSEL, nIdx2, 0) > 0) { - if (*pszBuffer) lstrcat(pszBuffer, "|"); - mySendMessage(hwnd, LB_GETTEXT, (WPARAM)nIdx2, (LPARAM)pszItem); - lstrcat(pszBuffer, pszItem); + *pszBuffer = '\0'; + int nNumItems = mySendMessage(hwnd, LB_GETCOUNT, 0, 0); + for (int nIdx2 = 0; nIdx2 < nNumItems; nIdx2++) { + if (mySendMessage(hwnd, LB_GETSEL, nIdx2, 0) > 0) { + if (*pszBuffer) lstrcat(pszBuffer, "|"); + mySendMessage(hwnd, LB_GETTEXT, (WPARAM)nIdx2, (LPARAM)pszItem); + lstrcat(pszBuffer, pszItem); + } + } + + FREE(pszItem); + break; + } + + case FIELD_TEXT: + case FIELD_FILEREQUEST: + case FIELD_DIRREQUEST: + case FIELD_COMBOBOX: + { + int nLength = mySendMessage(pField->hwnd, WM_GETTEXTLENGTH, 0, 0); + if (nLength > nBufLen) { + FREE(pszBuffer); + // add a bit extra so we do this less often + nBufLen = nLength + 20; + pszBuffer = (char*)MALLOC(nBufLen); + if (!pszBuffer) return false; + } + *pszBuffer='"'; + GetWindowText(hwnd, pszBuffer+1, nBufLen-1); + pszBuffer[nLength+1]='"'; + pszBuffer[nLength+2]='\0'; + + if ( pField->nType == FIELD_TEXT && (pField->nFlags & FLAG_MULTILINE) ) + { + char *pszBuf2 = (char*)MALLOC(nBufLen*2); // double the size, consider the worst case, all chars are \r\n + char *p1, *p2; + for (p1=pszBuffer,p2=pszBuf2; *p1; p1++, p2++) { + switch (*p1) { + case '\t': + *p2++ = '\\'; + *p2 = 't'; + break; + case '\n': + *p2++ = '\\'; + *p2 = 'n'; + break; + case '\r': + *p2++ = '\\'; + *p2 = 'r'; + break; + case '\\': + *p2++ = '\\'; + default: + *p2=*p1; } } - - FREE(pszItem); - break; - } - default: - { - int nLength = mySendMessage(pField->hwnd, WM_GETTEXTLENGTH, 0, 0); - if (nLength > nBufLen) { - FREE(pszBuffer); - // add a bit extra so we do this less often - nBufLen = nLength + 20; - pszBuffer = (char*)MALLOC(nBufLen); - if (!pszBuffer) return false; - } - *pszBuffer='"'; - GetWindowText(hwnd, pszBuffer+1, nBufLen-1); - pszBuffer[nLength+1]='"'; - pszBuffer[nLength+2]='\0'; - - if ( pField->nType == FIELD_TEXT && pField->nFlags & FLAG_MULTILINE ) - { - char *pszBuf2 = (char*)MALLOC(nBufLen*2); // double the size, consider the worst case, all chars are \r\n - char *p1, *p2; - for (p1=pszBuffer,p2=pszBuf2; *p1; p1++, p2++) { - switch (*p1) { - case '\r': - *p2++ = '\\'; - *p2 = 'r'; - break; - case '\n': - *p2++ = '\\'; - *p2 = 'n'; - break; - case '\t': - *p2++ = '\\'; - *p2 = 't'; - break; - case '\\': - *p2++ = '\\'; - default: - *p2=*p1; - } - } - *p2 = 0; - nBufLen = nBufLen*2; - FREE(pszBuffer); - pszBuffer=pszBuf2; - } - - break; + *p2 = 0; + nBufLen = nBufLen*2; + FREE(pszBuffer); + pszBuffer=pszBuf2; } + break; + } } wsprintf(szField, "Field %d", CurrField); WritePrivateProfileString(szField, "State", pszBuffer, pszFilename); - CurrField++; } // Tell NSIS which control was activated, if any @@ -356,7 +353,6 @@ bool WINAPI SaveSettings(void) { #define BROWSE_WIDTH 15 -#define BUFFER_SIZE 8192 // 8kb of mem is max char count in multiedit static char szResult[BUFFER_SIZE]; DWORD WINAPI myGetProfileString(LPCTSTR lpAppName, LPCTSTR lpKeyName) @@ -432,59 +428,52 @@ int WINAPI ReadSettings(void) { }; // Control flags static TableEntry FlagTable[] = { - { "RIGHT", FLAG_RIGHT }, + { "NOTIFY", LBS_NOTIFY }, { "WARN_IF_EXIST", OFN_OVERWRITEPROMPT }, { "FILE_HIDEREADONLY", OFN_HIDEREADONLY }, - { "DISABLED", FLAG_DISABLED }, - { "GROUP", FLAG_GROUP }, - { "NOTABSTOP", FLAG_NOTABSTOP }, + { "MULTISELECT", LBS_MULTIPLESEL }, + { "READONLY", FLAG_READONLY }, + { "RIGHT", BS_LEFTTEXT }, { "PASSWORD", FLAG_PASSWORD }, { "ONLY_NUMBERS", FLAG_ONLYNUMBERS }, { "MULTILINE", FLAG_MULTILINE }, - { "MULTISELECT", FLAG_MULTISELECT }, - { "EXTENDEDSELCT", FLAG_EXTENDEDSEL }, - { "FILE_MUST_EXIST", OFN_FILEMUSTEXIST }, + { "NOWORDWRAP", FLAG_NOWORDWRAP }, + { "WANTRETURN", FLAG_WANTRETURN }, + { "EXTENDEDSELCT", LBS_EXTENDEDSEL }, { "PATH_MUST_EXIST", OFN_PATHMUSTEXIST }, + { "FILE_MUST_EXIST", OFN_FILEMUSTEXIST }, { "PROMPT_CREATE", OFN_CREATEPROMPT }, { "DROPLIST", FLAG_DROPLIST }, { "RESIZETOFIT", FLAG_RESIZETOFIT }, - { "NOTIFY", FLAG_NOTIFY }, + { "NOTABSTOP", WS_TABSTOP }, + { "GROUP", WS_GROUP }, { "REQ_SAVE", FLAG_SAVEAS }, - { "NOWORDWRAP", FLAG_NOWORDWRAP }, { "FILE_EXPLORER", OFN_EXPLORER }, - { "WANTRETURN", FLAG_WANTRETURN }, - { "VSCROLL", FLAG_VSCROLL }, - { "HSCROLL", FLAG_HSCROLL }, - { "READONLY", FLAG_READONLY }, + { "HSCROLL", WS_HSCROLL }, + { "VSCROLL", WS_VSCROLL }, + { "DISABLED", WS_DISABLED }, { NULL, 0 } }; FieldType *pField = pFields + nIdx; wsprintf(szField, "Field %d", nCtrlIdx + 1); - myGetProfileString(szField, "TYPE"); // Get the control type + myGetProfileString(szField, "TYPE"); pField->nType = LookupToken(TypeTable, szResult); if (pField->nType == FIELD_INVALID) continue; // Lookup flags associated with the control type - pField->nFlags |= LookupToken(FlagTable, szResult); - - pField->pszText = myGetProfileStringDup(szField, "TEXT"); - - // Label Text - convert newline - - if (pField->nType == FIELD_LABEL) { - ConvertNewLines(pField->pszText); - } + pField->nFlags = LookupToken(FlagTable, szResult); + myGetProfileString(szField, "Flags"); + pField->nFlags |= LookupTokens(FlagTable, szResult); // pszState must not be NULL! myGetProfileString(szField, "State"); pField->pszState = strdup(szResult); - pField->pszRoot = myGetProfileStringDup(szField, "ROOT"); - + // ListBox items list { int nResult = myGetProfileString(szField, "ListItems"); if (nResult) { @@ -495,21 +484,24 @@ int WINAPI ReadSettings(void) { pField->pszListItems[nResult + 1] = '\0'; } } - pField->nMaxLength = myGetProfileInt(szField, "MaxLen", 0); - pField->nMinLength = myGetProfileInt(szField, "MinLen", 0); - pField->pszValidateText = myGetProfileStringDup(szField, "ValidateText"); + // Label Text - convert newline + pField->pszText = myGetProfileStringDup(szField, "TEXT"); + if (pField->nType == FIELD_LABEL) + ConvertNewLines(pField->pszText); + + // Dir request - root folder + pField->pszRoot = myGetProfileStringDup(szField, "ROOT"); // ValidateText - convert newline - - if (pField->pszValidateText) { - ConvertNewLines(pField->pszValidateText); - } + pField->pszValidateText = myGetProfileStringDup(szField, "ValidateText"); + ConvertNewLines(pField->pszValidateText); { int nResult = GetPrivateProfileString(szField, "Filter", "All Files|*.*", szResult, sizeof(szResult), pszFilename); if (nResult) { - // add an extra | character to the end to simplify the loop where we add the items. + // Convert the filter to the format required by Windows: NULL after each + // item followed by a terminating NULL pField->pszFilter = (char*)MALLOC(nResult + 2); strcpy(pField->pszFilter, szResult); char *pszPos = pField->pszFilter; @@ -521,15 +513,13 @@ int WINAPI ReadSettings(void) { } pField->rect.left = myGetProfileInt(szField, "LEFT", 0); - pField->rect.right = myGetProfileInt(szField, "RIGHT", 0); pField->rect.top = myGetProfileInt(szField, "TOP", 0); + pField->rect.right = myGetProfileInt(szField, "RIGHT", 0); pField->rect.bottom = myGetProfileInt(szField, "BOTTOM", 0); - - myGetProfileString(szField, "Flags"); - pField->nFlags |= LookupTokens(FlagTable, szResult); + pField->nMinLength = myGetProfileInt(szField, "MinLen", 0); + pField->nMaxLength = myGetProfileInt(szField, "MaxLen", 0); // Text color for LINK control, default is pure blue - //if (pField->nType == FIELD_LINK) pField->hImage = (HANDLE)myGetProfileInt(szField, "TxtColor", RGB(0,0,255)); pField->nControlID = 1200 + nIdx; @@ -538,7 +528,7 @@ int WINAPI ReadSettings(void) { FieldType *pNewField = &pFields[nIdx+1]; pNewField->nControlID = 1200 + nIdx + 1; pNewField->nType = FIELD_BROWSEBUTTON; - pNewField->nFlags = pField->nFlags & (FLAG_DISABLED | FLAG_NOTABSTOP); + pNewField->nFlags = pField->nFlags & (WS_DISABLED | WS_TABSTOP); pNewField->pszText = STRDUP(szBrowseButtonCaption); // needed for generic FREE pNewField->rect.right = pField->rect.right; pNewField->rect.left = pNewField->rect.right - BROWSE_WIDTH; @@ -556,7 +546,9 @@ int WINAPI ReadSettings(void) { LRESULT WINAPI WMCommandProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify) { switch (codeNotify) { - case BN_CLICKED: + case BN_CLICKED: // The user pressed a button + case LBN_SELCHANGE: // The user changed the selection in a ListBox control +// case CBN_SELCHANGE: // The user changed the selection in a DropList control (same value as LBN_SELCHANGE) { char szBrowsePath[MAX_PATH]; int nIdx = FindControlIdx(id); @@ -580,7 +572,7 @@ LRESULT WINAPI WMCommandProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify) tryagain: if ((pField->nFlags & FLAG_SAVEAS) ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn)) { - SetWindowText(pField->hwnd, szBrowsePath); + mySetWindowText(pField->hwnd, szBrowsePath); break; } else if (szBrowsePath[0] && CommDlgExtendedError() == FNERR_INVALIDFILENAME) { @@ -625,7 +617,7 @@ LRESULT WINAPI WMCommandProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify) break; if (SHGetPathFromIDList(pResult, szBrowsePath)) { - SetWindowText(pField->hwnd, szBrowsePath); + mySetWindowText(pField->hwnd, szBrowsePath); } LPMALLOC pMalloc; @@ -637,16 +629,20 @@ LRESULT WINAPI WMCommandProc(HWND hWnd, UINT id, HWND hwndCtl, UINT codeNotify) } case FIELD_LINK: - ShellExecute(hMainWindow, NULL, pField->pszState, NULL, NULL, SW_SHOWDEFAULT); + case FIELD_BUTTON: + // Allow the state to be empty - this might be useful in conjunction + // with the NOTIFY flag + if (*pField->pszState) + ShellExecute(hMainWindow, NULL, pField->pszState, NULL, NULL, SW_SHOWDEFAULT); break; } - if (pField->nFlags & FLAG_NOTIFY) { + if (pField->nFlags & LBS_NOTIFY) { // Remember which control was activated then pretend the user clicked Next g_NotifyField = nIdx + 1; // the next button must be enabled or nsis will ignore WM_COMMAND BOOL bWasDisabled = EnableWindow(hNextButton, TRUE); - FORWARD_WM_COMMAND(hMainWindow, IDOK, hNextButton, codeNotify, mySendMessage); + FORWARD_WM_COMMAND(hMainWindow, IDOK, hNextButton, BN_CLICKED, mySendMessage); if (bWasDisabled) EnableWindow(hNextButton, FALSE); } @@ -715,13 +711,12 @@ BOOL CALLBACK cfgDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) // We need lpdis->rcItem later RECT rc = lpdis->rcItem; - // Get TxtColor unless the user has set another using SetCtlColors - if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) - SetTextColor(lpdis->hDC, (COLORREF) pField->hImage); - // Calculate needed size of the control DrawText(lpdis->hDC, pField->pszText, -1, &rc, DT_VCENTER | DT_SINGLELINE | DT_CALCRECT); + // Make some more room so the focus rect won't cut letters off + rc.right = min(rc.right + 2, lpdis->rcItem.right); + // Move rect to right if in RTL mode if (bRTL) { @@ -729,19 +724,24 @@ BOOL CALLBACK cfgDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) rc.right += lpdis->rcItem.right - rc.right; } - // Make some more room so the focus rect won't cut letters off - rc.right = min(rc.right + 2, lpdis->rcItem.right); + if (lpdis->itemAction & ODA_DRAWENTIRE) + { + // Get TxtColor unless the user has set another using SetCtlColors + if (!GetWindowLong(lpdis->hwndItem, GWL_USERDATA)) + SetTextColor(lpdis->hDC, (COLORREF) pField->hImage); - // Draw the text - DrawText(lpdis->hDC, pField->pszText, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE | (bRTL ? DT_RTLREADING : 0)); + // Draw the text + DrawText(lpdis->hDC, pField->pszText, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE | (bRTL ? DT_RTLREADING : 0)); + } // Draw the focus rect if needed - if (((lpdis->itemState & ODS_FOCUS) && (lpdis->itemAction & ODA_DRAWENTIRE)) || (lpdis->itemAction & ODA_FOCUS) || (lpdis->itemAction & ODA_SELECT)) + if (((lpdis->itemState & ODS_FOCUS) && (lpdis->itemAction & ODA_DRAWENTIRE)) || (lpdis->itemAction & ODA_FOCUS)) { + // NB: when not in DRAWENTIRE mode, this will actually toggle the focus + // rectangle since it's drawn in a XOR way DrawFocusRect(lpdis->hDC, &rc); } - MapWindowPoints(lpdis->hwndItem, 0, (LPPOINT) &rc, 2); pField->rect = rc; #ifdef IO_LINK_UNDERLINED @@ -755,7 +755,7 @@ BOOL CALLBACK cfgDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_CTLCOLORBTN: case WM_CTLCOLORLISTBOX: // let the NSIS window handle colors, it knows best - return mySendMessage(hMainWindow, WM_CTLCOLORSTATIC, wParam, lParam); + return mySendMessage(hMainWindow, uMsg, wParam, lParam); } return 0; } @@ -766,31 +766,44 @@ BOOL CALLBACK cfgDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) #define IDC_HAND MAKEINTRESOURCE(32649) #endif +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000FL +#endif + // pFields[nIdx].nParentIdx is used to store original windowproc int WINAPI StaticLINKWindowProc(HWND hWin, UINT uMsg, LPARAM wParam, WPARAM lParam) { - int CtrlId = GetDlgCtrlID(hWin); - int StaticField = FindControlIdx(CtrlId); + int StaticField = FindControlIdx(GetDlgCtrlID(hWin)); if (StaticField < 0) return 0; FieldType *pField = pFields + StaticField; switch(uMsg) { - case WM_SETFOCUS: - mySendMessage(hConfigWindow, DM_SETDEFID, CtrlId, 0); - // remove the BS_DEFPUSHBUTTON style from IDOK - mySendMessage(GetDlgItem(hMainWindow, IDOK), BM_SETSTYLE, BS_PUSHBUTTON, TRUE); - break; - case WM_NCHITTEST: + case WM_GETDLGCODE: + // Pretend we are a normal button/default button as appropriate + return DLGC_BUTTON | ((pField->nFlags & FLAG_WANTRETURN) ? DLGC_DEFPUSHBUTTON : DLGC_UNDEFPUSHBUTTON); + + case BM_SETSTYLE: + // Detect when we are becoming the default button but don't lose the owner-draw style + if ((wParam & BS_TYPEMASK) == BS_DEFPUSHBUTTON) + pField->nFlags |= FLAG_WANTRETURN; // Hijack this flag to indicate default button status + else + pField->nFlags &= ~FLAG_WANTRETURN; + wParam = (wParam & ~BS_TYPEMASK) | BS_OWNERDRAW; + break; + + case WM_NCHITTEST: { POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + MapWindowPoints(0, hWin, &pt, 1); if (PtInRect(&pField->rect, pt)) return HTCLIENT; else return HTNOWHERE; } - case WM_SETCURSOR: + + case WM_SETCURSOR: { if ((HWND)wParam == hWin && LOWORD(lParam) == HTCLIENT) { @@ -838,12 +851,12 @@ int WINAPI createCfgDlg() } hCancelButton = GetDlgItem(mainwnd,IDCANCEL); - hInitialFocus = hNextButton = GetDlgItem(mainwnd,IDOK); + hNextButton = GetDlgItem(mainwnd,IDOK); hBackButton = GetDlgItem(mainwnd,3); - if (pszCancelButtonText) SetWindowText(hCancelButton,pszCancelButtonText); - if (pszNextButtonText) SetWindowText(hNextButton,pszNextButtonText); - if (pszBackButtonText) SetWindowText(hBackButton,pszBackButtonText); + mySetWindowText(hCancelButton,pszCancelButtonText); + mySetWindowText(hNextButton,pszNextButtonText); + mySetWindowText(hBackButton,pszBackButtonText); if (bBackEnabled!=-1) EnableWindow(hBackButton,bBackEnabled); if (bCancelEnabled!=-1) EnableWindow(hCancelButton,bCancelEnabled); @@ -894,6 +907,8 @@ int WINAPI createCfgDlg() DeleteDC(memDC); + BOOL fFocused = FALSE; + #define DEFAULT_STYLES (WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) for (int nIdx = 0; nIdx < nNumFields; nIdx++) { @@ -905,18 +920,18 @@ int WINAPI createCfgDlg() DWORD dwRTLExStyle; } ClassTable[] = { { "STATIC", // FIELD_LABEL - DEFAULT_STYLES /*| WS_TABSTOP*/, - DEFAULT_STYLES | SS_RIGHT /*| WS_TABSTOP*/, + DEFAULT_STYLES, + DEFAULT_STYLES | SS_RIGHT, WS_EX_TRANSPARENT, WS_EX_TRANSPARENT | WS_EX_RTLREADING }, { "STATIC", // FIELD_ICON - DEFAULT_STYLES /*| WS_TABSTOP*/ | SS_ICON, - DEFAULT_STYLES /*| WS_TABSTOP*/ | SS_ICON, + DEFAULT_STYLES | SS_ICON, + DEFAULT_STYLES | SS_ICON, 0, WS_EX_RTLREADING }, { "STATIC", // FIELD_BITMAP - DEFAULT_STYLES /*| WS_TABSTOP*/ | SS_BITMAP | SS_CENTERIMAGE, - DEFAULT_STYLES /*| WS_TABSTOP*/ | SS_BITMAP | SS_CENTERIMAGE, + DEFAULT_STYLES | SS_BITMAP | SS_CENTERIMAGE, + DEFAULT_STYLES | SS_BITMAP | SS_CENTERIMAGE, 0, WS_EX_RTLREADING }, { "BUTTON", // FIELD_BROWSEBUTTON @@ -1025,9 +1040,7 @@ int WINAPI createCfgDlg() break; case FIELD_CHECKBOX: case FIELD_RADIOBUTTON: - case FIELD_BUTTON: - if (pField->nFlags & FLAG_RIGHT) - dwStyle |= BS_RIGHTBUTTON; + dwStyle ^= pField->nFlags & BS_LEFTTEXT; break; case FIELD_TEXT: case FIELD_FILEREQUEST: @@ -1036,40 +1049,37 @@ int WINAPI createCfgDlg() dwStyle |= ES_PASSWORD; if (pField->nFlags & FLAG_ONLYNUMBERS) dwStyle |= ES_NUMBER; + if (pField->nFlags & FLAG_WANTRETURN) + dwStyle |= ES_WANTRETURN; + if (pField->nFlags & FLAG_READONLY) + dwStyle |= ES_READONLY; + title = pField->pszState; if (pField->nFlags & FLAG_MULTILINE) { dwStyle |= ES_MULTILINE | ES_AUTOVSCROLL; // Enable word-wrap unless we have a horizontal scroll bar // or it has been explicitly disallowed - if (!(pField->nFlags & (FLAG_HSCROLL | FLAG_NOWORDWRAP))) + if (!(pField->nFlags & (WS_HSCROLL | FLAG_NOWORDWRAP))) dwStyle &= ~ES_AUTOHSCROLL; ConvertNewLines(pField->pszState); + // If multiline-readonly then hold the text back until after the + // initial focus has been set. This is so the text is not initially + // selected - useful for License Page look-a-likes. + if (pField->nFlags & FLAG_READONLY) + title = NULL; } - if (pField->nFlags & FLAG_WANTRETURN) - dwStyle |= ES_WANTRETURN; - if (pField->nFlags & FLAG_VSCROLL) - dwStyle |= WS_VSCROLL; - if (pField->nFlags & FLAG_HSCROLL) - dwStyle |= WS_HSCROLL; - if (pField->nFlags & FLAG_READONLY) - dwStyle |= ES_READONLY; - title = pField->pszState; break; case FIELD_COMBOBOX: dwStyle |= (pField->nFlags & FLAG_DROPLIST) ? CBS_DROPDOWNLIST : CBS_DROPDOWN; title = pField->pszState; break; case FIELD_LISTBOX: - if (pField->nFlags & FLAG_EXTENDEDSEL) - dwStyle |= LBS_EXTENDEDSEL; - if (pField->nFlags & FLAG_MULTISELECT) - dwStyle |= LBS_MULTIPLESEL; + dwStyle |= pField->nFlags & (LBS_NOTIFY | LBS_MULTIPLESEL | LBS_EXTENDEDSEL); break; } - if (pField->nFlags & FLAG_DISABLED) dwStyle |= WS_DISABLED; - if (pField->nFlags & FLAG_GROUP) dwStyle |= WS_GROUP; - if (pField->nFlags & FLAG_NOTABSTOP) dwStyle &= ~WS_TABSTOP; + dwStyle |= pField->nFlags & (WS_GROUP | WS_HSCROLL | WS_VSCROLL | WS_DISABLED); + if (pField->nFlags & WS_TABSTOP) dwStyle &= ~WS_TABSTOP; HWND hwCtrl = pField->hwnd = CreateWindowEx( dwExStyle, @@ -1090,15 +1100,20 @@ int WINAPI createCfgDlg() // Sets the font of IO window to be the same as the main window mySendMessage(hwCtrl, WM_SETFONT, (WPARAM)hFont, TRUE); // Set initial focus to the first appropriate field - if ((hInitialFocus == hNextButton) && (dwStyle & WS_TABSTOP)) - hInitialFocus = hwCtrl; + if (!fFocused && (dwStyle & (WS_TABSTOP | WS_DISABLED)) == WS_TABSTOP) { + fFocused = TRUE; + mySetFocus(hwCtrl); + } // make sure we created the window, then set additional attributes switch (pField->nType) { case FIELD_TEXT: - mySendMessage(hwCtrl, WM_SETTEXT, 0, (LPARAM)title); - // no break; case FIELD_FILEREQUEST: case FIELD_DIRREQUEST: + // If multiline-readonly then hold the text back until after the + // initial focus has been set. This is so the text is not initially + // selected - useful for License Page look-a-likes. + if ((pField->nFlags & (FLAG_MULTILINE | FLAG_READONLY)) == (FLAG_MULTILINE | FLAG_READONLY)) + mySetWindowText(hwCtrl, pField->pszState); mySendMessage(hwCtrl, EM_LIMITTEXT, (WPARAM)pField->nMaxLength, (LPARAM)0); break; @@ -1139,7 +1154,7 @@ int WINAPI createCfgDlg() } FREE(pszList); if (pField->pszState) { - if (pField->nFlags & (FLAG_MULTISELECT|FLAG_EXTENDEDSEL) && nFindMsg == LB_FINDSTRINGEXACT) { + if (pField->nFlags & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && nFindMsg == LB_FINDSTRINGEXACT) { mySendMessage(hwCtrl, LB_SETSEL, FALSE, -1); pszStart = pszEnd = pField->pszState; while (*pszStart) { @@ -1212,8 +1227,10 @@ int WINAPI createCfgDlg() } } - if (pszTitle) - SetWindowText(mainwnd,pszTitle); + if (!fFocused) + mySetFocus(hNextButton); + + mySetWindowText(mainwnd,pszTitle); pFilenameStackEntry = *g_stacktop; *g_stacktop = (*g_stacktop)->next; static char tmp[32]; @@ -1229,7 +1246,6 @@ void WINAPI showCfgDlg() // Tell NSIS to remove old inner dialog and pass handle of the new inner dialog mySendMessage(hMainWindow, WM_NOTIFY_CUSTOM_READY, (WPARAM)hConfigWindow, 0); ShowWindow(hConfigWindow, SW_SHOWNA); - SetFocus(hInitialFocus); g_done = g_NotifyField = 0; @@ -1364,15 +1380,15 @@ void WINAPI ConvertNewLines(char *str) { for (p1=p2=str; *p1; p1++, p2++) { if (*p1 == '\\') { switch (p1[1]) { + case 't': + *p2 = '\t'; + break; case 'n': *p2 = '\n'; break; case 'r': *p2 = '\r'; break; - case 't': - *p2 = '\t'; - break; case '\\': *p2 = '\\'; break; diff --git a/Contrib/InstallOptions/Readme.html b/Contrib/InstallOptions/Readme.html index 02e3362d..23de0fa0 100644 --- a/Contrib/InstallOptions/Readme.html +++ b/Contrib/InstallOptions/Readme.html @@ -235,10 +235,12 @@ will display a directory requester where the user can browse for a directory.
Icon" control displays an icon. Use no Text to use the installer icon.
A "Bitmap" control displays a bitmap.
A "GroupBox" control displays a frame to group controls.
-A "Link" control displays a static hot text, when the user click the control the contents -of State (e.g. http://...) will be executed using ShellExecute
-A "Button" control displays a push button that your NSIS script can act on when pressed. -See the "NOTIFY" flag for more information. +A "Link" control displays a static hot text. When the user clicks the control the contents +of State (e.g. http://...) will be executed using ShellExecute. Alternatively +State can be omitted and the NOTIFY flag used to have your NSIS script +called. See the "NOTIFY" flag below for more information.
+A "Button" control displays a push button that can be used in the same way as the +"Link" control above. Text @@ -257,7 +259,8 @@ are NSIS functions for converting text to/from this format. window, so you can read from it from NSIS. For edit texts and dir and file request boxes, this is the string that is specified. For radio button and check boxes, this can be '0' or '1' (for unchecked or checked). For list boxes, combo boxes and drop lists this is the selected items -separated by pipes ('|').
+separated by pipes ('|'). For Links and Buttons this can specify something to be executed or opened +(using ShellExecute).

Note: For Text fields with the MULTILINE flag, \r\n will be converted to a newline. To use a back-slash in your text you have to escape it using another back-slash - \\. @@ -314,16 +317,19 @@ Bottom set in dialog units. To get the right dimensions for your controls, design your dialog using a resource editor and copy the dimensions to the INI file.

-Note: For combobox or droplist, the "bottom" value is not used in the +Note: You can specify negative coordinates to specify the distance from the right +or bottom edge.
+
+Note (2): For combobox or droplist, the "bottom" value is not used in the same way.
In this case, the bottom value is the maximum size of the window when the pop-up list is being displayed. All other times, the combobox is automatically sized to be one element tall. If you have trouble where you can not see the combobox drop-down, then check the bottom value and ensure it is -large enough.
+large enough. A rough guide for the height required is the number of items in the list multiplied +by 8, plus 20.

-Note (2): FileRequest and DirRequest controls will allocate 15 dialog units to the -browse button. Make this control wide enough the contents of the textbox can be seen. Note that you -can specify negative coordinates to specify the distance from the right or bottom edge. +Note (3): FileRequest and DirRequest controls will allocate 15 dialog units to the +browse button. Make this control wide enough the contents of the textbox can be seen. Filter @@ -461,13 +467,12 @@ HSCROLL flag also has this effect. HSCROLL -Used by "Text" controls with multiple-line. Show a horizontal -scrollbar and disable word-wrap. +Show a horizontal scrollbar. When used by "Text" controls with +multiple-lines this also disables word-wrap. VSCROLL -Used by "Text" controls with multiple-line. Show a vertical -scrollbar. +Show a vertical scrollbar. READONLY @@ -476,11 +481,12 @@ text in the edit control, but allow the user to select and copy the text. NOTIFY -Used by "Button", "CheckBox" and "RadioButton" -controls. Causes InstallOptions to call your NSIS custom page validation/leave function whenever -the button is pressed. Your validation/leave function can read the "State" value from the -"Settings" section to determine which custom button has been pressed, if any, and perform -some appropriate action followed by an Abort instruction (to tell NSIS to return to the page). The +Used by "Button", "Link", "CheckBox", +"RadioButton", "ListBox" and "DropList" controls. Causes InstallOptions +to call your NSIS custom page validation/leave function whenever the control's selection changes. +Your validation/leave function can read the "State" value from the "Settings" +section to determine which control caused the notification, if any, and perform some appropriate +action followed by an Abort instruction (to tell NSIS to return to the page). The Contrib\InstallOptions folder contains an example script showing how this might be used. @@ -718,12 +724,32 @@ FunctionEnd

Version history

+ diff --git a/Contrib/InstallOptions/testlink.ini b/Contrib/InstallOptions/testlink.ini index 9619815d..592ae930 100644 --- a/Contrib/InstallOptions/testlink.ini +++ b/Contrib/InstallOptions/testlink.ini @@ -1,5 +1,5 @@ [Settings] -NumFields=4 +NumFields=5 [Field 1] Type=Label @@ -34,3 +34,11 @@ Top=70 Bottom=80 State=http://nsis.sourceforge.net/ Text=* Homepage http://nsis.sourceforge.net/ + +[Field 5] +Type=Text +Left=20 +Right=-40 +Top=85 +Bottom=98 +State=Just to test proper interaction with the other fields diff --git a/Contrib/InstallOptions/testnotify.ini b/Contrib/InstallOptions/testnotify.ini index 3bea95c7..f86fc8e5 100644 --- a/Contrib/InstallOptions/testnotify.ini +++ b/Contrib/InstallOptions/testnotify.ini @@ -1,5 +1,5 @@ [Settings] -NumFields=9 +NumFields=11 [Field 1] Type=Groupbox @@ -31,6 +31,7 @@ Bottom=38 [Field 4] Type=Checkbox Text=Install support for Z +Flags=RIGHT State=0 Left=10 Right=100 @@ -73,12 +74,32 @@ Right=-10 Top=97 Bottom=118 MinLen=1 +ValidateText=Please enter some text before proceeding. [Field 9] Type=Button Flags=NOTIFY -Text=Clear +Text=&Clear Left=-60 Right=-10 -Top=27 -Bottom=41 +Top=19 +Bottom=33 + +[Field 10] +Type=Button +Text=&Email +State=mailto:someone@anywhere.com +Left=-60 +Right=-10 +Top=35 +Bottom=49 + +[Field 11] +Type=DROPLIST +ListItems=Show|Hide +State=Show +Flags=NOTIFY +Left=120 +Right=-80 +Top=20 +Bottom=56 diff --git a/Contrib/InstallOptions/testnotify.nsi b/Contrib/InstallOptions/testnotify.nsi index 40cd06ba..17764c49 100644 --- a/Contrib/InstallOptions/testnotify.nsi +++ b/Contrib/InstallOptions/testnotify.nsi @@ -30,6 +30,8 @@ Page custom ShowCustom LeaveCustom ": Testing InstallOptions" Function ShowCustom ; Initialise the dialog but don't show it yet + MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Test the right-to-left version?" IDNO +2 + WriteINIStr "$PLUGINSDIR\test.ini" "Settings" "RTL" "1" InstallOptions::initDialog /NOUNLOAD "$PLUGINSDIR\test.ini" ; In this mode InstallOptions returns the window handle so we can use it Pop $hwnd @@ -45,9 +47,10 @@ Function LeaveCustom ; At this point the user has either pressed Next or one of our custom buttons ; We find out which by reading from the INI file ReadINIStr $0 "$PLUGINSDIR\test.ini" "Settings" "State" - StrCmp $0 0 validate ; Next button? - StrCmp $0 2 supportx ; "Install support for X"? - StrCmp $0 9 clearbtn ; "Clear" button? + StrCmp $0 0 validate ; Next button? + StrCmp $0 2 supportx ; "Install support for X"? + StrCmp $0 9 clearbtn ; "Clear" button? + StrCmp $0 11 droplist ; "Show|Hide" drop-list? Abort ; Return to the page supportx: @@ -65,10 +68,23 @@ clearbtn: SendMessage $1 ${WM_SETTEXT} 0 "STR:" GetDlgItem $1 $hwnd 1206 ; DirRequest control (1200 + field 6 - 1 + 1 browse button) SendMessage $1 ${WM_SETTEXT} 0 "STR:" - GetDlgItem $1 $hwnd 1209 ; DirRequest control (1200 + field 8 - 1 + 2 browse buttons) + GetDlgItem $1 $hwnd 1209 ; Multiline control (1200 + field 8 - 1 + 2 browse buttons) SendMessage $1 ${WM_SETTEXT} 0 "STR:" Abort ; Return to the page +droplist: + ; Make the DirRequest field depend on the droplist + ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 11" "State" + StrCmp $0 "Show" +3 + StrCpy $0 0 + Goto +2 + StrCpy $0 1 + GetDlgItem $1 $hwnd 1206 ; DirRequest control (1200 + field 6 - 1 + 1 browse button) + EnableWindow $1 $0 + GetDlgItem $1 $hwnd 1207 ; ... button (the following control) + EnableWindow $1 $0 + Abort ; Return to the page + validate: ; At this point we know the Next button was pressed, so perform any validation ReadINIStr $0 "$PLUGINSDIR\test.ini" "Field 2" "State" diff --git a/Plugins/InstallOptions.dll b/Plugins/InstallOptions.dll index c139277f86842d613aa2d00fd3dda68354c14e8b..7555d80609300042d44debc9fc7fb01aaee88578 100644 GIT binary patch delta 7035 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 T-a2F*K?X_joMVsQxrO;(6b&M* delta 7175 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