This is neccessary according to the Twain specification and also allows much more comfortable user interfaces.
The Twain specification is clear that event processing in the main application must be provided while the data source shows it's User Interface:
``` "You cannot use the modal dialog creation call DialogBox( ) to create the Source’s user interface main window. To allow event processing by both the application and the Source, this call cannot be used" ```
This was already documented that way in Twain Specification 1.8.
According to the specification, MSG_ENABLEDS only creates the dialog and then returns. But the implementation in ui.c with the call to CreatePropertySheetW equals a "DialogBox" call and returns only when the user clicks OK or CANCEL.
This Merge Requests sets PSH_MODELESS to make CreatePropertySheetW create a non-modal dialog and then returns control back to the application.
To avoid crashes, some of the pointers passed to CreatePropertySheetW must stay valid as long as the dialog remains open. A pointer to an additional struct SUiData is stored with the activeDS and cleared when the user interface is cleared with DestroyWindow on MSG_DISABLEDS. To reduce the number of changes in the source code, the PROPSHEETHEADERW is not part of the struct as no problems were observable.
The user interface is no longer automatically closed after every scan. Depening on the application program, it can stay open, ready for the next scan with just a click on the "Scan" button, which results in a much easier and faster user interface experience.
For example the user might modifiy settings like resolution or brightness and start a second scan with the modified parameters with a singe click.
The progress dialog is modified
* to use the UI dialog as it's parent window. * And get created centered on the user interface dialog instead of the screen.
Application orograms I found to profit from the modified behaviour:
* IrfanView 4.7.2 if the Button "Close Twain dialog after acquiring an image" is disabled. * The simple memxfer.c source I recently attached to an older merge request.
From: Bernd Herd codeberg@herdsoft.com
This is neccessary according to the Twain specification and also allows much more comfortable user interfaces. --- dlls/sane.ds/ds_ctrl.c | 34 ++++++---- dlls/sane.ds/ds_image.c | 3 + dlls/sane.ds/sane.rc | 2 +- dlls/sane.ds/sane_i.h | 9 +++ dlls/sane.ds/sane_main.c | 17 ++++- dlls/sane.ds/ui.c | 138 +++++++++++++++++++++++++++++++++++---- 6 files changed, 175 insertions(+), 28 deletions(-)
diff --git a/dlls/sane.ds/ds_ctrl.c b/dlls/sane.ds/ds_ctrl.c index 64f505d7712..13055b9c30e 100644 --- a/dlls/sane.ds/ds_ctrl.c +++ b/dlls/sane.ds/ds_ctrl.c @@ -186,6 +186,10 @@ TW_UINT16 SANE_ProcessEvent (pTW_IDENTITY pOrigin, twRC = TWRC_FAILURE; activeDS.twCC = TWCC_SEQERROR; } + else if (UI_IsDialogMessage(pMsg)) + { + twRC = TWRC_DSEVENT; + }
return twRC; } @@ -218,8 +222,6 @@ TW_UINT16 SANE_PendingXfersEndXfer (pTW_IDENTITY pOrigin, pPendingXfers->Count = 0; activeDS.currentState = 5; SANE_Cancel(); - /* Notify the application that it can close the data source */ - SANE_Notify(MSG_CLOSEDSREQ); } else { @@ -235,8 +237,6 @@ TW_UINT16 SANE_PendingXfersEndXfer (pTW_IDENTITY pOrigin, pPendingXfers->Count = 0; activeDS.currentState = 5; SANE_Cancel(); - /* Notify the application that it can close the data source */ - SANE_Notify(MSG_CLOSEDSREQ); } } twRC = TWRC_SUCCESS; @@ -291,7 +291,6 @@ TW_UINT16 SANE_PendingXfersGet (pTW_IDENTITY pOrigin, pPendingXfers->Count = 0; activeDS.currentState = 5; SANE_Cancel(); - SANE_Notify(MSG_CLOSEDSREQ); } twRC = TWRC_SUCCESS; activeDS.twCC = TWCC_SUCCESS; @@ -378,6 +377,8 @@ TW_UINT16 SANE_DisableDSUserInterface (pTW_IDENTITY pOrigin, } else { + UI_Destroy(); + activeDS.currentState = 4; twRC = TWRC_SUCCESS; activeDS.twCC = TWCC_SUCCESS; @@ -405,19 +406,28 @@ TW_UINT16 SANE_EnableDSUserInterface (pTW_IDENTITY pOrigin, { activeDS.hwndOwner = pUserInterface->hParent; activeDS.ShowUI = pUserInterface->ShowUI; + activeDS.ModalUI = FALSE; if (pUserInterface->ShowUI) { - BOOL rc; activeDS.currentState = 5; /* Transitions to state 5 */ - rc = DoScannerUI(); - pUserInterface->ModalUI = TRUE; - if (!rc) + if (!DoScannerUI()) { - SANE_Notify(MSG_CLOSEDSREQ); + twRC = TWRC_FAILURE; + activeDS.twCC = TWCC_BUMMER; } - else + + /* Since Twain 1.9, the ModalUI value set by the application has a meaning, + * before that, that struct member was only used for DS -> App. */ + activeDS.ModalUI = pUserInterface->ModalUI + && activeDS.hwndOwner + && (pOrigin->ProtocolMajor * 100 + pOrigin->ProtocolMinor)>=109 + && IsWindowEnabled(activeDS.hwndOwner); + + pUserInterface->ModalUI = activeDS.ModalUI; + + if (activeDS.ModalUI) { - get_sane_params( &activeDS.frame_params ); + EnableWindow(activeDS.hwndOwner, FALSE); } } else diff --git a/dlls/sane.ds/ds_image.c b/dlls/sane.ds/ds_image.c index f2b0a294531..0486b191f1e 100644 --- a/dlls/sane.ds/ds_image.c +++ b/dlls/sane.ds/ds_image.c @@ -125,6 +125,7 @@ TW_UINT16 SANE_Start(void) /* If starting the scan failed, cancel scan job */ if (twRC != TWRC_SUCCESS) { + UI_Enable(TRUE); activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); activeDS.twCC = TWCC_OPERATIONERROR; return TWRC_FAILURE; @@ -134,6 +135,7 @@ TW_UINT16 SANE_Start(void) { WARN("sane_get_parameters failed\n"); SANE_CALL( cancel_device, NULL ); + UI_Enable(TRUE); activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); activeDS.twCC = TWCC_OPERATIONERROR; return TWRC_FAILURE; @@ -177,6 +179,7 @@ TW_UINT16 SANE_Start(void) void SANE_Cancel(void) { SANE_CALL( cancel_device, NULL ); + UI_Enable(TRUE); activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); activeDS.currentState = 5; } diff --git a/dlls/sane.ds/sane.rc b/dlls/sane.ds/sane.rc index d853fade567..4d94aa05a39 100644 --- a/dlls/sane.ds/sane.rc +++ b/dlls/sane.ds/sane.rc @@ -50,7 +50,7 @@ END
IDD_SCANNING DIALOG 53, 50, 186, 104 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE | DS_CENTER | DS_SETFOREGROUND +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_CENTER | DS_SETFOREGROUND CAPTION "Scanning" FONT 8, "MS Shell Dlg" BEGIN diff --git a/dlls/sane.ds/sane_i.h b/dlls/sane.ds/sane_i.h index 8a1a742605e..b2b4349ac26 100644 --- a/dlls/sane.ds/sane_i.h +++ b/dlls/sane.ds/sane_i.h @@ -68,6 +68,12 @@ struct tagActiveDS
/* TRUE if user interface dialog is shown in DG_CONTROL/DAT_USERINTERFACE/MSG_ENABLEDS */ BOOL ShowUI; + + /* Data strored for the property sheet dialog while it is displayed */ + struct SUiData * ui_data; + + /* TRUE if the DS makes the parent window Modal */ + BOOL ModalUI; };
extern struct tagActiveDS activeDS; @@ -213,6 +219,9 @@ TW_UINT16 SANE_RGBResponseSet /* UI function */ BOOL DoScannerUI(void); HWND ScanningDialogBox(HWND dialog, LONG progress); +BOOL UI_IsDialogMessage(MSG *msg); +void UI_Destroy(void); +void UI_Enable(BOOL enable);
/* Option functions */ TW_UINT16 sane_option_get_value( int optno, void *val ); diff --git a/dlls/sane.ds/sane_main.c b/dlls/sane.ds/sane_main.c index 6b8f1e23f89..ad9268f14ad 100644 --- a/dlls/sane.ds/sane_main.c +++ b/dlls/sane.ds/sane_main.c @@ -95,6 +95,21 @@ static TW_UINT16 SANE_OpenDS( pTW_IDENTITY pOrigin, pTW_IDENTITY self)
static TW_UINT16 SANE_SetEntryPoint (pTW_IDENTITY pOrigin, TW_MEMREF pData);
+ +/** @brief Close the data source. + * Closes all associated windows and frees memory. + */ +static void SANE_CloseDS(void) +{ + if(activeDS.progressWnd) + { + ScanningDialogBox(activeDS.progressWnd, -1); + } + SANE_CALL( close_ds, NULL ); + UI_Destroy(); +} + + static TW_UINT16 SANE_SourceControlHandler ( pTW_IDENTITY pOrigin, TW_UINT16 DAT, @@ -109,7 +124,7 @@ static TW_UINT16 SANE_SourceControlHandler ( switch (MSG) { case MSG_CLOSEDS: - SANE_CALL( close_ds, NULL ); + SANE_CloseDS(); break; case MSG_OPENDS: twRC = SANE_OpenDS( pOrigin, (pTW_IDENTITY)pData); diff --git a/dlls/sane.ds/ui.c b/dlls/sane.ds/ui.c index 7ac816f20b5..36047803a3c 100644 --- a/dlls/sane.ds/ui.c +++ b/dlls/sane.ds/ui.c @@ -485,20 +485,37 @@ exit: return tpl; }
+/** Data stored for the property sheet dialog while it is open */ +struct SUiData +{ + /** Window handle of the property sheet */ + HWND hwPropertySheet; + + /** Number of property sheet pages */ + int page_count; + + /** Room for property sheet pages */ + PROPSHEETPAGEW psp[20]; +}; + BOOL DoScannerUI(void) { HDC hdc; - PROPSHEETPAGEW psp[10]; + PROPSHEETPAGEW *psp; int page_count= 0; PROPSHEETHEADERW psh; int index = 1; TW_UINT16 rc; int optcount; - UINT psrc; LPWSTR szCaption; DWORD len;
- memset(psp,0,sizeof(psp)); + activeDS.ui_data = (struct SUiData *) calloc(1, sizeof(struct SUiData)); + if (!activeDS.ui_data) + { + return FALSE; + } + psp = activeDS.ui_data->psp; rc = sane_option_get_value( 0, &optcount ); if (rc != TWCC_SUCCESS) { @@ -534,6 +551,7 @@ BOOL DoScannerUI(void)
index ++; } + activeDS.ui_data->page_count = page_count;
len = lstrlenA(activeDS.identity.Manufacturer) + lstrlenA(activeDS.identity.ProductName) + 2; @@ -544,7 +562,7 @@ BOOL DoScannerUI(void) MultiByteToWideChar(CP_ACP,0,activeDS.identity.ProductName,-1, &szCaption[lstrlenA(activeDS.identity.Manufacturer)+1],len); psh.dwSize = sizeof(PROPSHEETHEADERW); - psh.dwFlags = PSH_PROPSHEETPAGE|PSH_PROPTITLE|PSH_USECALLBACK; + psh.dwFlags = PSH_MODELESS|PSH_PROPSHEETPAGE|PSH_PROPTITLE|PSH_USECALLBACK; psh.hwndParent = activeDS.hwndOwner; psh.hInstance = SANE_instance; psh.pszIcon = 0; @@ -554,23 +572,87 @@ BOOL DoScannerUI(void) psh.ppsp = (LPCPROPSHEETPAGEW)psp; psh.pfnCallback = PropSheetProc;
- psrc = PropertySheetW(&psh); + activeDS.ui_data->hwPropertySheet = (HWND) PropertySheetW(&psh);
- for(index = 0; index < page_count; index ++) + if (!activeDS.ui_data->hwPropertySheet) { - free((LPBYTE)psp[index].pResource); - free((LPBYTE)psp[index].pszTitle); + UI_Destroy(); } free(szCaption);
DeleteDC(hdc);
- if (psrc == IDOK) - return TRUE; - else - return FALSE; + return activeDS.ui_data != NULL; +} + + +/** Check if a Message is addressed to the property sheet dialog + */ +BOOL +UI_IsDialogMessage(MSG *msg) +{ + return + activeDS.ui_data && + activeDS.ui_data->hwPropertySheet && + SendMessageW(activeDS.ui_data->hwPropertySheet, PSM_ISDIALOGMESSAGE, 0, (LPARAM) msg); +} + + +/** Destroy the property sheet dialog and associated structures + */ +void +UI_Destroy(void) +{ + if (activeDS.ui_data) + { + if(activeDS.ui_data->hwPropertySheet) + { + DestroyWindow(activeDS.ui_data->hwPropertySheet); + } + for(int index = 0; index < activeDS.ui_data->page_count; index ++) + { + free((LPBYTE)activeDS.ui_data->psp[index].pResource); + free((LPBYTE)activeDS.ui_data->psp[index].pszTitle); + } + free(activeDS.ui_data); + activeDS.ui_data = NULL; + } + if (activeDS.ModalUI) + { + EnableWindow(activeDS.hwndOwner, TRUE); + } +} + +/** + * @brief control enable state of scan dialog + * When finished scanning, re-enable the UI Dialog. + * + * @param enable TRUE to enable, FALSE to disable + */ +void +UI_Enable(BOOL enable) +{ + HWND hwndControl; + + if (activeDS.ui_data && + activeDS.ui_data->hwPropertySheet) + { + EnableWindow(activeDS.ui_data->hwPropertySheet, enable); + + // Give the user a bit of optical feedback + if (NULL != (hwndControl=GetDlgItem(activeDS.ui_data->hwPropertySheet, IDOK))) + { + EnableWindow(hwndControl, enable); + } + if (NULL != (hwndControl=GetDlgItem(activeDS.ui_data->hwPropertySheet, IDCANCEL))) + { + EnableWindow(hwndControl, enable); + } + } }
+ + static BOOL save_to_reg( DWORD reg_type, CHAR* name, const BYTE* value, DWORD size ) { HKEY h_key; @@ -1020,7 +1102,12 @@ static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM case PSN_APPLY: if (psn->lParam) { - SANE_XferReady(); + if (IsWindowEnabled(activeDS.ui_data->hwPropertySheet)) + { + SANE_XferReady(); + /* Disable the DS UI while scanning */ + UI_Enable(FALSE); + } } break; case PSN_QUERYCANCEL: @@ -1105,7 +1192,30 @@ HWND ScanningDialogBox(HWND dialog, LONG progress)
if (!dialog) { - dialog = CreateDialogW(SANE_instance, MAKEINTRESOURCEW(IDD_SCANNING), NULL, ScanningProc); + HWND hwndOwner= + activeDS.ui_data + ? activeDS.ui_data->hwPropertySheet + : NULL; + dialog = CreateDialogW(SANE_instance, MAKEINTRESOURCEW(IDD_SCANNING), hwndOwner, ScanningProc); + + if (dialog) + { + if (hwndOwner) + { + RECT rcDialog, rcOwner; + GetWindowRect(dialog, &rcDialog); + GetWindowRect(hwndOwner, &rcOwner); + SetWindowPos(dialog, NULL, + (rcOwner.right+rcOwner.left)/2 - (rcDialog.right-rcDialog.left)/2, + (rcOwner.bottom+rcOwner.top)/2 - (rcDialog.bottom-rcDialog.top)/2, + 0, 0, + SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOZORDER); + } + else + { + ShowWindow(dialog, SW_SHOW); + } + } }
if (progress == -1)
This merge request was approved by Esme Povirk.