//---------------------------------------------------------------------------- // EgalTech 2026-2026 //---------------------------------------------------------------------------- // File : LUA_GenDialog.cpp Data : 06.07.26 Versione : 3.1g2 // Contenuto : Funzioni gestione dialoghi per LUA. // // // // Modifiche : 06.07.26 DS Creazione modulo. // // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "DllMain.h" #include "EXE.h" #include "LUA.h" #include "LUA_GenDialog.h" #include "resource.h" #include "/EgtDev/Include/ExeExecutor.h" #include "/EgtDev/Include/EGkLuaAux.h" #include "/EgtDev/Include/EGkStringUtils3d.h" #include "/EgtDev/Include/EgtIniFile.h" #include "/EgtDev/Include/EgtNumUtils.h" #include #include using namespace std ; //---------------------------------------------------------------------------- // Costanti const int MAX_CTRLS = IDC_TEXT8 - IDC_TEXT1 + 1 ; const int OFFS_CTRLS = 40 ; enum TYPE_CTRLS { CTRL_NONE = 0, CTRL_CHECK = 1, CTRL_COMBO = 2, CTRL_EDIT = 3, CTRL_COLOR = 4, CTRL_BUTTON = 5} ; const char* SEC_SCENE = "Scene" ; const char* KEY_CUSTOMCOLORS = "CustomColors" ; // Tipi struct DialogContext { bool bDone = false ; // flag per chiusura dialogo (X/Ok/Cancel) bool bCancelled = false ; // true se X/Cancel STRVECTOR vsResult ; // valori finali da ritornare a Lua } ; // Variabili Globali static COLORREF s_CustomColors[16] = {12632256} ; // 16 colori GRAY static bool s_bCustomColorsInit = false ; // flag di inizializzazione colori static HWND s_hDlg = NULL ; static string s_sCaption ; static int s_nCtrls = 0 ; static string s_sText[MAX_CTRLS] ; static string s_sEdit[MAX_CTRLS] ; static int s_nType[MAX_CTRLS] ; static bool s_bSelectionMode = false ; // se true allora diventa dialogo "Modeless" static int s_nActiveRow = - 1 ; // indice della riga di "SB:" attiva static int s_nDlgItem = -1 ; // indice dell'Item di selezione //------------------------------------------------------------------------------- // OnClosingDialog // // Scopo: // Gestisce la chiusura del dialogo modeless in modo centralizzato e sicuro. // Questa funzione viene chiamata quando l’utente chiude il dialogo tramite: // - OK // - Cancel // - pulsante X // - WM_CLOSE // // Comportamento: // - Riabilita la finestra principale (Main Window), che era stata disabilitata durante il funzionamento // modale del dialogo. // - Distrugge la finestra del dialogo. // - Riporta il focus alla finestra principale, garantendo continuità nell’interazione dell’utente. // - Deseleziona tutti gli oggetti nella scena e aggiorna la vista, in modo che nessuna selezione residua // rimanga attiva dopo la chiusura. //------------------------------------------------------------------------------- static void OnClosingDialog( HWND hwndDlg) { // reset stato globale s_bSelectionMode = false ; s_nActiveRow = -1 ; s_nDlgItem = -1 ; // il controllo passa al Main HWND hMain = GetParent( hwndDlg) ; EnableWindow( hMain, TRUE) ; DestroyWindow( hwndDlg) ; s_hDlg = NULL ; SetFocus( hMain) ; // deseleziono tutto ExeDeselectAll() ; ExeDraw() ; } //------------------------------------------------------------------------------- // StartSelectionMode // // Scopo: // Attiva la modalità di selezione (modeless) del dialogo. Questa modalità permette all’utente di interagire // con la scena mentre il dialogo rimane aperto e attivo. // // Parametri: // hDlg → handle del dialogo // nEditId → ID dell’edit associato al pulsante BS premuto // nActiveRow → indice della riga BS attiva (0..MAX_CTRLS-1) //------------------------------------------------------------------------------- static void StartSelectionMode( HWND hDlg, int nEditId, int nActiveRow) { // il dialogo torna modeless rispetto alla scena HWND hMain = GetParent( hDlg) ; EnableWindow( hMain, TRUE) ; // resta comunque in primo piano SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) ; // disabilito tutti gli edit associati agli SB tranne quello attivo for ( int i = 0; i < s_nCtrls ; ++ i) { if ( s_sEdit[i].find( "SB:") == 0) { int idEdit = IDC_EDIT1 + i ; EnableWindow( GetDlgItem( hDlg, idEdit), ( i == nActiveRow ? TRUE : FALSE)) ; } } // seleziono solo gli elementi presenti nell'edit corrispondente s_nDlgItem = - 1 ; ExeDeselectAll() ; // !<-- NB. avendo messo s_nDlgItem = -1 il testo non viene rimosso s_bSelectionMode = true ; s_nActiveRow = nActiveRow ; s_nDlgItem = nEditId ; AtoWEX<128> wsEdit( "") ; GetDlgItemText( hDlg, nEditId, LPWSTR( wsEdit), 128) ; string sIds = wstrztoA( wsEdit) ; STRVECTOR vsIds ; Tokenize( sIds, ",", vsIds) ; for ( const string& sId : vsIds) { int nId = GDB_ID_NULL ; if ( FromString( sId, nId) && nId != GDB_ID_NULL) ExeSelectObj( nId) ; } ExeDraw() ; } //------------------------------------------------------------------------------- // EndSelectionMode // // Scopo: // Termina la modalità di selezione e riporta il dialogo in stato modale. //------------------------------------------------------------------------------- static void EndSelectionMode( HWND hDlg) { s_bSelectionMode = false ; s_nActiveRow = -1 ; s_nDlgItem = -1 ; // disabilito tutti gli edit associati ai BS for ( int i = 0; i < s_nCtrls ; ++ i) { if ( s_sEdit[i].find( "SB:") == 0) { int idEdit = IDC_EDIT1 + i ; EnableWindow( GetDlgItem( hDlg, idEdit), FALSE) ; SetDlgItemText( hDlg, IDC_BUTTON_SEL1 + i, strztoW( "+")) ; } } // il dialogo torna modale HWND hMain = GetParent( hDlg) ; EnableWindow( hMain, FALSE) ; SetFocus( hDlg) ; // deseleziono tutto ExeDeselectAll() ; //!<-- NB. avendo messo nDlgModelessItem = -1 il testo non viene rimosso ExeDraw() ; } //----------------------------------------------------------------------------- LRESULT CALLBACK UpdateDialogSelection( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { if ( msg == WM_GETDLGCODE) return DLGC_WANTALLKEYS ; if ( msg == WM_KEYDOWN && wParam == VK_RETURN) { // dwRefData contiene l'ID dell'EDIT int nEditId = int( dwRefData) ; HWND hwndDlg = GetParent(hwnd) ; if ( hwndDlg == nullptr) return FALSE ; // recupero il testo dell'EDIT corretto string sIds ; AtoWEX<128> wsEdit( "") ; if ( GetDlgItemText( hwndDlg, nEditId, LPWSTR( wsEdit), 128) > 0) sIds = wstrztoA( wsEdit) ; // deseleziono tutto ExeDeselectAll() ; // seleziono gli ID contenuti if ( ! sIds.empty()) { STRVECTOR vsIds; Tokenize( sIds, ",", vsIds) ; for ( const string& s : vsIds) { int nId = GDB_ID_NULL ; if ( FromString( s, nId) && nId != GDB_ID_NULL) ExeSelectObj( nId) ; } } // aggiorno la grafica ed esco ExeDraw() ; return TRUE ; } return ( DefSubclassProc( hwnd, msg, wParam, lParam)) ; } //------------------------------------------------------------------------------- BOOL CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { // variabili static locali per mantenere lo stato tra chiamate per selezione colori static COLORREF selectedColor[MAX_CTRLS] = {0} ; // BLACK static HBRUSH hColorBrush[MAX_CTRLS] = {NULL} ; // non definito switch ( message) { case WM_INITDIALOG : { // imposto il dialogo come modale HWND hMain = GetParent( hwndDlg) ; EnableWindow( hMain, FALSE) ; SetFocus( hwndDlg) ; // imposto il contesto della funzione per recuperalo in futuro (per la funzione di CallBack Lua) SetWindowLongPtr( hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam) ; // colore di default COLORREF defaultColor = RGB( 255, 0, 0) ; // inizializzazione colori e brush for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { selectedColor[i] = defaultColor ; if ( hColorBrush[i] != NULL) DeleteObject( hColorBrush[i]) ; hColorBrush[i] = CreateSolidBrush( selectedColor[i]) ; } // dati del dialogo HWND hwndOwner = GetParent( hwndDlg) ; if ( hwndOwner == nullptr) hwndOwner = GetDesktopWindow() ; RECT rcOwner, rcDlg ; GetWindowRect( hwndOwner, &rcOwner) ; GetWindowRect( hwndDlg, &rcDlg) ; // determino la posizione dell'ultima riga di dati da visualizzare HWND hwndLR = GetDlgItem( hwndDlg, IDC_TEXT1 + Clamp( s_nCtrls, 1, MAX_CTRLS) - 1) ; RECT rcLR ; GetWindowRect( hwndLR, &rcLR) ; POINT ptLR{ rcLR.left, rcLR.top} ; ScreenToClient( hwndDlg, &ptLR) ; // centro e scalo il dialogo int nNewH = rcLR.top - rcOwner.top + 2 * OFFS_CTRLS ; // non chiaro perchè va sottratto rcOwner.top ma funziona sempre SetWindowPos( hwndDlg, HWND_TOP, ( rcOwner.left + rcOwner.right - ( rcDlg.right - rcDlg.left)) / 2, ( rcOwner.top + rcOwner.bottom - nNewH) / 2, ( rcDlg.right - rcDlg.left), nNewH, 0) ; // riposiziono il bottone Ok HWND hwndOk = GetDlgItem( hwndDlg, IDOK) ; RECT rcOk ; GetWindowRect( hwndOk, &rcOk) ; POINT ptOk{ rcOk.left, rcOk.top} ; ScreenToClient( hwndDlg, &ptOk) ; SetWindowPos( hwndOk, hwndDlg, ptOk.x, ptLR.y + OFFS_CTRLS, 0, 0, SWP_NOSIZE | SWP_NOZORDER) ; // riposiziono il bottone Cancel HWND hwndCl = GetDlgItem( hwndDlg, IDCANCEL) ; RECT rcCl ; GetWindowRect( hwndCl, &rcCl) ; POINT ptCl{ rcCl.left, rcCl.top} ; ScreenToClient( hwndDlg, &ptCl) ; SetWindowPos( hwndCl, hwndDlg, ptCl.x, ptLR.y + OFFS_CTRLS, 0, 0, SWP_NOSIZE | SWP_NOZORDER) ; // stile finestra LONG style = GetWindowLong( hwndDlg, GWL_STYLE) ; style |= WS_SYSMENU ; SetWindowLong( hwndDlg, GWL_STYLE, style) ; } // assegno titolo del dialogo SetWindowText( hwndDlg, stringtoW( s_sCaption)) ; // assegno testi e valori di default e nascondo linee dei controlli non usati for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { if ( i < s_nCtrls) { // imposto il testo nella riga corrente SetDlgItemText( hwndDlg, IDC_TEXT1 + i, stringtoW( s_sText[i])) ; // --- se CheckBox if ( s_sEdit[i].find( "CK:") == 0) { bool bChecked = false ; FromString( s_sEdit[i].substr( 3), bChecked) ; CheckDlgButton( hwndDlg, IDC_CHECK1 + i, ( bChecked ? BST_CHECKED : BST_UNCHECKED)) ; ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; // nascondo Edit ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; // nascondo Combo ShowWindow( GetDlgItem( hwndDlg, IDC_BUTTON_SEL1 + i), SW_HIDE) ; // nascondo Button di selezione s_nType[i] = CTRL_CHECK ; } // --- se ComboBox else if ( s_sEdit[i].find( "CB:") == 0) { string sList = s_sEdit[i].substr( 3) ; STRVECTOR vsVal ; Tokenize( sList, ",", vsVal) ; int nSel = 0 ; HWND hwndCBox = GetDlgItem( hwndDlg, IDC_COMBO1 + i) ; for ( int j = 0 ; j < ssize( vsVal) ; ++ j) { string sItem ; if ( vsVal[j][0] == '*') { nSel = j ; sItem = vsVal[j].substr( 1) ; } else sItem = vsVal[j] ; ComboBox_AddString( hwndCBox, stringtoW( sItem)) ; } ComboBox_SetCurSel( hwndCBox, nSel) ; int nEditHeight = int( SendMessage( hwndCBox, CB_GETITEMHEIGHT, (WPARAM)-1, 0)) ; int nItemHeight = int( SendMessage( hwndCBox, CB_GETITEMHEIGHT, 0, 0)) ; int nTotalHeight = nEditHeight + nItemHeight * ( ssize( vsVal) + 1) ; RECT rc ; GetWindowRect( hwndCBox, &rc) ; HWND parent = GetParent(hwndCBox) ; MapWindowPoints( NULL, parent, (LPPOINT)&rc, 2) ; SetWindowPos( hwndCBox, NULL, rc.left, rc.top, rc.right - rc.left, nTotalHeight, SWP_NOZORDER) ; ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; // nascondo Edit ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; // nascondo CheckBox ShowWindow( GetDlgItem( hwndDlg, IDC_BUTTON_SEL1 + i), SW_HIDE) ; // nascondo Button di selezione s_nType[i] = CTRL_COMBO ; } // --- se ColorPicker else if ( s_sEdit[i].find( "CP:") == 0) { string sVal = s_sEdit[i].substr( 3) ; Color CColor ; if ( ! FromString( sVal, CColor)) CColor = INVISIBLE ; selectedColor[i] = RGB( CColor.GetIntRed(), CColor.GetIntGreen(), CColor.GetIntBlue()) ; if ( hColorBrush[i] != NULL) DeleteObject( hColorBrush[i]) ; hColorBrush[i] = CreateSolidBrush( selectedColor[i]) ; // recupero i valori dei colori personalizzati dal file .ini if ( ! s_bCustomColorsInit) { // recupero il file Ini string sIniFile = ExeGetIniFile() ; if ( ! sIniFile.empty()) { string sCustomVals = GetPrivateProfileStringUtf8( SEC_SCENE, KEY_CUSTOMCOLORS, "", sIniFile.c_str()) ; STRVECTOR vsWinColors ; Tokenize( sCustomVals, ",", vsWinColors) ; for ( int j = 0 ; j < ssize( vsWinColors) && j < 16 ; ++ j) { unsigned int unVal ; if ( FromString( vsWinColors[j], unVal)) { unsigned int unR = unVal & 0xFF ; unsigned int unG = ( unVal >> 8) & 0xFF ; unsigned int unB = ( unVal >> 16) & 0xFF ; s_CustomColors[j] = RGB( unR, unG, unB) ; } } } s_bCustomColorsInit = true ; } // nascondo qualsiasi tipo di componente ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; // nascondo Edit ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; // nascondo Combo ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; // nascondo Check ShowWindow( GetDlgItem( hwndDlg, IDC_BUTTON_SEL1 + i), SW_HIDE) ; // nascondo Button di selezione // margini per dimensione del rettangolo preview pickColor const int PREVIEW_HEIGHT = 20 ; // altezza del rettangolo const int PREVIEW_MIN_WIDTH = 40 ; // larghezza minima // creazione della static preview HWND hwndPreview = NULL ; HWND hwndEdit = GetDlgItem( hwndDlg, IDC_EDIT1 + i) ; RECT rcEdit ; GetClientRect( hwndDlg, &rcEdit) ; // coordinate client del dialog if ( hwndEdit != nullptr && GetWindowRect( hwndEdit, &rcEdit)) { // converti rcLabel in coordinate client POINT ptEdit = { rcEdit.left, rcEdit.top } ; ScreenToClient( hwndDlg, &ptEdit) ; int x = ptEdit.x ; int y = ptEdit.y ; int width = rcEdit.right - rcEdit.left ; if ( width < PREVIEW_MIN_WIDTH) width = PREVIEW_MIN_WIDTH ; hwndPreview = CreateWindowEx( 0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_CENTERIMAGE | WS_BORDER, x, y, width, PREVIEW_HEIGHT, hwndDlg, ( HMENU)INT_PTR( IDC_COLOR1 + i), GetModuleHandle( NULL), NULL) ; } // pulizia elementi testuali if ( hwndPreview) { SetWindowText( hwndPreview, L"") ; SetWindowPos( hwndPreview, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) ; InvalidateRect( hwndPreview, NULL, TRUE) ; UpdateWindow( hwndPreview) ; } s_nType[i] = CTRL_COLOR ; } // --- se Button di selezione else if ( s_sEdit[i].find( "SB:") == 0) { ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; // nascondo la Combo ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; // nascondo la Check SetDlgItemText( hwndDlg, IDC_BUTTON_SEL1 + i, strztoW( "+")) ; // modalità non Selezione EnableWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), FALSE) ; // disattivo la Edit // associo alla casella di testo la funzione di KeyPress su Enter HWND hEdit = GetDlgItem( hwndDlg, IDC_EDIT1 + i) ; if ( hEdit != nullptr) SetWindowSubclass( hEdit, UpdateDialogSelection, 1, (DWORD_PTR)( IDC_EDIT1 + i)) ; s_nType[i] = CTRL_BUTTON ; } // --- se Testo else { SetDlgItemText( hwndDlg, IDC_EDIT1 + i, stringtoW( s_sEdit[i])) ; ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; // nascondo Combo ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; // nascondo Check ShowWindow( GetDlgItem( hwndDlg, IDC_BUTTON_SEL1 + i), SW_HIDE) ; // nascondo Button di selezione s_nType[i] = CTRL_EDIT ; } } // se numero di controllo superiore al massimo, non visualizzo nullo ( in questo caso aggiungere elementi a Dialog in .rc) else { // nascondo tutto ShowWindow( GetDlgItem( hwndDlg, IDC_TEXT1 + i), SW_HIDE) ; ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; ShowWindow( GetDlgItem( hwndDlg, IDC_BUTTON_SEL1 + i), SW_HIDE) ; s_nType[i] = CTRL_NONE ; } } break ; case WM_CTLCOLORSTATIC : { HDC hdcStatic = ( HDC)wParam ; HWND hwndCtrl = ( HWND)lParam ; int ctrlId = GetDlgCtrlID( hwndCtrl) ; if ( ctrlId >= IDC_COLOR1 && ctrlId <= IDC_COLOR8) { int idx = ctrlId - IDC_COLOR1 ; if ( hColorBrush[idx] == NULL) hColorBrush[idx] = CreateSolidBrush( selectedColor[idx]) ; SetBkMode( hdcStatic, OPAQUE) ; SetBkColor( hdcStatic, selectedColor[idx]) ; return ( INT_PTR)hColorBrush[idx] ; } } break ; case WM_COMMAND : { int id = LOWORD( wParam) ; int code = HIWORD( wParam) ; // --- se click di un Button di selezione if ( id >= IDC_BUTTON_SEL1 && id < IDC_BUTTON_SEL1 + MAX_CTRLS) { int nBtnId = id ; int nEditId = IDC_EDIT1 + ( id - IDC_BUTTON_SEL1) ; int nActiveRow = nEditId - IDC_EDIT1 ; // se il bottone premuto è lo stesso if ( s_nDlgItem == nEditId) { // se selezione attiva, allora deve terminare if ( s_bSelectionMode) { SetDlgItemText( hwndDlg, nBtnId, strztoW( "+")) ; EndSelectionMode( hwndDlg) ; } // se selezione non attiva, allora viene attivata else { SetDlgItemText( hwndDlg, nBtnId, strztoW( "S")) ; StartSelectionMode( hwndDlg, nEditId, nActiveRow) ; } } // se nuovo buttone premuto else { // se esisteva una selezione precedente, questa viene annullata if ( s_nDlgItem != -1) { // recupero il Button di selezione precedente e modifico il suo testo int nPrevBtnId = IDC_BUTTON_SEL1 + ( s_nDlgItem - IDC_EDIT1) ; SetDlgItemText( hwndDlg, nPrevBtnId, strztoW( "+")) ; } // la selezione passa al comando corrente s_nDlgItem = nEditId ; SetDlgItemText( hwndDlg, nBtnId, strztoW( "S")) ; // il dialogo diventa "Modeless" temporaneamente s_nActiveRow = nEditId - IDC_EDIT1 ; StartSelectionMode( hwndDlg, nEditId, nActiveRow) ; } return TRUE ; // !<-- esco } // --- se comando di Preview per brush di colori mediante click del mouse else if ( code == STN_CLICKED && id >= IDC_COLOR1 && id <= IDC_COLOR8) { // se selezione attiva, allora deve terminare if ( s_bSelectionMode) { // se esisteva una selezione precedente, questa viene annullata if ( s_nDlgItem != -1) { // recupero il Button di selezione precedente e modifico il suo testo int nPrevBtnId = IDC_BUTTON_SEL1 + ( s_nDlgItem - IDC_EDIT1) ; SetDlgItemText( hwndDlg, nPrevBtnId, strztoW( "+")) ; } EndSelectionMode( hwndDlg) ; } int idx = id - IDC_COLOR1 ; HWND hwndPreview = GetDlgItem( hwndDlg, IDC_COLOR1 + idx) ; if ( ! hwndPreview) return TRUE ; // apertura ChooseColor (usa hwndDlg come owner) CHOOSECOLOR cc = {} ; cc.lStructSize = sizeof( cc) ; cc.hwndOwner = hwndDlg ; cc.lpCustColors = s_CustomColors ; cc.rgbResult = selectedColor[idx] ; cc.Flags = CC_FULLOPEN | CC_RGBINIT ; if ( ChooseColor( &cc)) { selectedColor[idx] = cc.rgbResult ; // salvo nel file Ini i nuovi colori personalizzati string sIniFile = ExeGetIniFile() ; if ( ! sIniFile.empty()) { string sOut = "" ; for ( int j = 0 ; j < ssize( s_CustomColors) && j < 16 ; ++ j) sOut += ToString( ( unsigned int)s_CustomColors[j]) + "," ; sOut.pop_back() ; WritePrivateProfileStringUtf8( SEC_SCENE, KEY_CUSTOMCOLORS, sOut.c_str(), sIniFile.c_str()) ; } if ( hColorBrush[idx] != NULL) DeleteObject( hColorBrush[idx]) ; hColorBrush[idx] = CreateSolidBrush( selectedColor[idx]) ; InvalidateRect( hwndPreview, NULL, TRUE) ; UpdateWindow( hwndPreview) ; } return TRUE ; // !<-- Esco } switch ( LOWORD( wParam)) { case IDOK : { // recupero il contesto impostato alla creazione della finestra (durante esecuzione WM_INITDIALOG) DialogContext* pdcCtx = (DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; if ( pdcCtx != nullptr) { // inizializzo i risultati pdcCtx->vsResult.clear() ; pdcCtx->vsResult.reserve( s_nCtrls) ; // recupero i valori degli Item for ( int i = 0 ; i < s_nCtrls ; ++ i) { AtoWEX<128> wsEdit( "") ; s_sEdit[i] = "" ; switch ( s_nType[i]) { case CTRL_CHECK : s_sEdit[i] = ( IsDlgButtonChecked( hwndDlg, IDC_CHECK1 + i) == BST_CHECKED ? "1" : "0") ; break ; case CTRL_COMBO : if ( GetDlgItemText( hwndDlg, IDC_COMBO1 + i, LPWSTR( wsEdit), 128) > 0) s_sEdit[i] = wstrztoA( wsEdit) ; break ; case CTRL_EDIT : if ( GetDlgItemText( hwndDlg, IDC_EDIT1 + i, LPWSTR( wsEdit), 128) > 0) s_sEdit[i] = wstrztoA( wsEdit) ; break ; case CTRL_COLOR : { COLORREF col = selectedColor[i] ; BYTE r = GetRValue( col), g = GetGValue( col), b = GetBValue( col) ; char buf[16] ; sprintf_s( buf, sizeof( buf), "%u,%u,%u", r, g, b) ; s_sEdit[i] = buf ; } break ; case CTRL_BUTTON : if ( GetDlgItemText( hwndDlg, IDC_EDIT1 + i, LPWSTR( wsEdit), 128) > 0) s_sEdit[i] = wstrztoA( wsEdit) ; break ; } // inserisco i valori all'interno della tabella Lua pdcCtx->vsResult.push_back( s_sEdit[i]) ; } pdcCtx->bCancelled = false ; pdcCtx->bDone = true ; } // chiudo il dialogo OnClosingDialog( hwndDlg) ; return TRUE ; } [[fallthrough]] ; case IDCANCEL : { // pulizia brush colori for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { if ( hColorBrush[i] != NULL) { DeleteObject( hColorBrush[i]) ; hColorBrush[i] = NULL ; } } // recupero il contesto impostato alla creazione della finestra ( durante esecuzione WM_INITDIALOG) e se esiste lo rimuovo DialogContext* pdcCtx = ( DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; if ( pdcCtx != nullptr) { pdcCtx->bCancelled = true ; pdcCtx->bDone = true ; } // chiudo il dialogo OnClosingDialog( hwndDlg) ; return TRUE ; } } // se siamo in modalità di Selezione e il comando attivo non si riferisce ad un button di Selezione (SB), il dialogo torna modale if ( s_bSelectionMode) { bool bIsBS = ( id >= IDC_BUTTON_SEL1 && id < IDC_BUTTON_SEL1 + MAX_CTRLS) ; bool bIsActiveEdit = ( id == IDC_EDIT1 + s_nActiveRow) ; // il dialogo torna modale if ( ! bIsBS && ! bIsActiveEdit) EndSelectionMode( hwndDlg) ; } break ; } case WM_CLOSE : { // recupero il contesto impostato alla creazione della finestra ( durante esecuzione WM_INITDIALOG) e se esiste lo rimuovo DialogContext* pdcCtx = (DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; if ( pdcCtx != nullptr) { pdcCtx->bCancelled = true ; pdcCtx->bDone = true ; } // chiudo il dialogo OnClosingDialog( hwndDlg) ; return TRUE ; } } return FALSE ; } //----------------------------------------------------------------------------- bool UpdateDialogIds( void) { // verifico presenza dialogo e item di selezione if ( s_hDlg == NULL || s_nDlgItem == -1) return false ; // recupero tutti gli elementi selezionati string sIds ; int nId = ExeGetFirstSelectedObj() ; while ( nId != GDB_ID_NULL) { sIds.append( ToString( nId) + ",") ; nId = ExeGetNextSelectedObj() ; } if ( ! sIds.empty()) sIds.pop_back() ; // aggiorno il contenuto nel dialogo return ( SetDlgItemText( s_hDlg, s_nDlgItem, stringtoW( sIds))) ; } //------------------------------------------------------------------------------- // LuaDialogBox (Modeless) // // Scopo: // ------ // Questa funzione implementa un dialogo "modale" dal punto di vista del codice Lua, ma "modeless" dal punto di vista della // UI Windows quando necessario. L’obiettivo è permettere al codice Lua di rimanere sospeso finché l’utente non chiude il // dialogo (OK/Cancel/X), mantenendo però la UI attiva e permettendo selezioni nella scena quando l’utente preme un pulsante // SB (Select Button). // // Perché serve: // ------------- // - I dialoghi modali standard (DialogBox) bloccano completamente la UI, mentre i dialoghi modeless standard (CreateDialog) // NON bloccano il codice Lua. // - Le coroutine Lua (lua_yield/lua_resume) non sono utilizzabili in questo contesto perché il thread principale // dell’applicazione non può essere sospeso tramite yield: il codice Lua non riprende correttamente e la UI non si // aggiorna coerentemente) // // Come funziona: // -------------- // 1) Il dialogo viene creato con CreateDialogParam(), quindi la UI rimane attiva. // 2) La funzione NON ritorna subito: entra in un message loop locale (Esattamente equivalente al loop all'interno di // DialogBox()). Il cuore del loop è GetMessage(), che è una chiamata BLOCCANTE. Significa che il thread rimane in // attesa senza consumare CPU finché non arriva un messaggio. Non c’è polling, non c’è busy loop. Il comportamento è // identico a quello di un dialogo modale nativo. // 3) Il ciclo rimane attivo finché: // - l’utente non chiude il dialogo (OK/Cancel/X) // - oppure la finestra viene distrutta // In quel momento ctx->bDone viene impostato a true. // 4) Durante la vita del dialogo, se l’utente preme un pulsante SB, il dialogo diventa temporaneamente "modeless": // - la main window viene abilitata // - il dialogo rimane abilitato ma passa in secondo piano logico // - l’utente può selezionare oggetti nella scena // Quando la selezione termina, il dialogo torna modale. // 5) Quando il dialogo chiude, i valori vengono copiati in ctx->vsResult e ritornati a Lua come risultato della funzione. // // Vantaggi della soluzione: // ------------------------- // - Lua rimane bloccato come con un dialogo modale tradizionale. // - La UI rimane completamente attiva. // - Le selezioni SB (mediante Select Button) funzionano senza bloccare il programma. // - Nessun uso di coroutine Lua (che non erano affidabili in questo contesto). // - Nessun consumo CPU anomalo. // - Comportamento stabile e prevedibile. // // Nota importante: // ---------------- // Il dialogo NON viene mai disabilitato durante la selezione SB (Select Button). Solo la main window viene // abilitata temporaneamente. // Questo è fondamentale: disabilitare il dialogo impedirebbe all’utente di poterlo riselezionare dopo la selezione nella scena. // //------------------------------------------------------------------------------- int LuaDialogBox( lua_State* L) { // 1 .. 9 parametri : sCaption, { sText, sDefault}, ... LuaCheckParam( L, 1, s_sCaption) s_nCtrls = 0 ; for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { STRVECTOR vData ; if ( LuaGetParam( L, 2 + i, vData) && ssize( vData) >= 2) { s_sText[i] = Trim( vData[0]) ; s_sEdit[i] = Trim( vData[1]) ; ++ s_nCtrls ; } else break ; } LuaClearStack( L) ; // se UI disabilitata, esco if ( ! ExeGetEnableUI()) { LuaSetParam( L) ; return 1 ; } // se dialogo già aperto, errore if ( s_hDlg != NULL) { LuaSetParam( L) ; return 1 ; } // definizione del contesto DialogContext dcCtx ; // creazione di un dialogo Modeless HWND hMainWnd = ExeGetMainWindowHandle() ; s_nDlgItem = -1 ; s_hDlg = ( CreateDialogParam( GetModuleIstance(), MAKEINTRESOURCE( IDD_LUADLG), hMainWnd, (DLGPROC)DialogBoxProc, (LPARAM) &dcCtx)) ; if ( s_hDlg == NULL) { LuaSetParam( L) ; return 1 ; } // porto la finestra in primo piano ShowWindow( s_hDlg, SW_SHOW) ; SetForegroundWindow( s_hDlg) ; // contesto corrente RECT rctWnd{ 0, 0, 0, 0} ; GseContext* pGseCtx = GetCurrGseContext() ; if ( pGseCtx != nullptr) GetWindowRect( pGseCtx->m_hWnd, &rctWnd) ; // message Loop MSG msg ; while ( ! dcCtx.bDone && IsWindow( s_hDlg)) { if ( GetMessage( &msg, NULL, 0, 0) <= 0) break ; if ( ! IsDialogMessage( s_hDlg, &msg) && ( ! s_bSelectionMode || PtInRect( &rctWnd, msg.pt) != FALSE)) { TranslateMessage( &msg) ; DispatchMessage( &msg) ; } } // ritorno i valori lua if ( dcCtx.bCancelled) LuaSetParam( L) ; else LuaSetParam( L, dcCtx.vsResult) ; return 1 ; }