diff --git a/AuxDialogBox.cpp b/AuxDialogBox.cpp new file mode 100644 index 0000000..072200e --- /dev/null +++ b/AuxDialogBox.cpp @@ -0,0 +1,91 @@ +//---------------------------------------------------------------------------- +// EgalTech 2026-2026 +//---------------------------------------------------------------------------- +// File : AuxDialogBox.cpp Data : 24.04.26 Versione : 3.1d4 +// Contenuto : Funzioni DialogBox. +// +// +// Modifiche : 24.04.26 RE Creazione modulo. +// +// +//---------------------------------------------------------------------------- + +//--------------------------- Include ---------------------------------------- +#include "stdafx.h" +#include "EXE.h" +#include "EXE_Macro.h" +#include "AuxDialogBox.h" +#include "/EgtDev/Include/EGnStringUtils.h" +#include "/EgtDev/Include/EXeExecutor.h" +#include "/EgtDev/Include/EGkStringUtils3d.h" +#include "/EgtDev/Include/EgtStringConverter.h" + +using namespace std ; + +#pragma comment( lib, "Comctl32.lib") // per funzioni LRESULT CALLBACK + +HWND phDlgModeless = nullptr ; +int nDlgModelessItem = -1 ; + +//----------------------------------------------------------------------------- +bool +UpdateIdsModelessDialog( HWND phDlgModeless, int nDlgModelessItem) +{ + // se DialogBox Modeless non inizializzato, esco + if ( phDlgModeless == nullptr) + return false ; + // se Id del controllo da aggiornare non valido, esco + if ( nDlgModelessItem == -1) + return false ; + + // recupero tutti gli elementi selezionati + string sIds ; + int nId = ExeGetFirstSelectedObj() ; + while ( nId != GDB_ID_NULL) { + sIds.append( ToString( nId) + string{","}) ; + nId = ExeGetNextSelectedObj() ; + } + if ( ! sIds.empty()) + sIds.pop_back() ; + + // aggiorno il contenuto nel dialogo + return ( SetDlgItemText( phDlgModeless, nDlgModelessItem, stringtoW( sIds))) ; +} + +//----------------------------------------------------------------------------- +LRESULT +CALLBACK UpdateSelectionModelessDialog( 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)) ; +} diff --git a/AuxDialogBox.h b/AuxDialogBox.h new file mode 100644 index 0000000..25466fc --- /dev/null +++ b/AuxDialogBox.h @@ -0,0 +1,31 @@ +//---------------------------------------------------------------------------- +// EgalTech 2026-2026 +//---------------------------------------------------------------------------- +// File : AuxDialogBox.h Data : 24.04.2026 Versione : 3.1d4 +// Contenuto : Prototipi funzioni DialogBox. +// +// +// +// Modifiche : 22.04.26 RE Creazione modulo. +// +// +//---------------------------------------------------------------------------- + +#pragma once + +#include "stdafx.h" +#include "resource.h" +#include "/EgtDev/Include/ExeExecutor.h" +#include "/EgtDev/Include/EGkLuaAux.h" +#include + +//---------------------------------------------------------------------------- +// Identificativo della finestra ModeLess corrente ( nullptr se non esiste) +extern HWND phDlgModeless ; +extern int nDlgModelessItem ; + +// Funzione per passaggi di parametri tra Selezione DB e dialogo non modale +bool UpdateIdsModelessDialog( HWND phDlgModeless, int UpdateIdsModelessDialog) ; + +//Funzione per selezionare gli Id presenti all'interno dei un EditText +LRESULT CALLBACK UpdateSelectionModelessDialog( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) ; diff --git a/EXE_GdbObjSelection.cpp b/EXE_GdbObjSelection.cpp index 4f9441f..cd7c3af 100644 --- a/EXE_GdbObjSelection.cpp +++ b/EXE_GdbObjSelection.cpp @@ -15,6 +15,7 @@ #include "stdafx.h" #include "EXE.h" #include "EXE_Macro.h" +#include "AuxDialogBox.h" #include "/EgtDev/Include/EXeExecutor.h" #include "/EgtDev/Include/EGkStringUtils3d.h" #include "/EgtDev/Include/EgtStringConverter.h" @@ -76,6 +77,9 @@ ExeSelectObj( int nId) " -- Ok=" + ToString( bOk) ; LOG_INFO( GetCmdLogger(), sLua.c_str()) ; } + // verifico se presente un dialogo modeless per aggiornare i valori + if ( bOk && phDlgModeless != nullptr && nDlgModelessItem != -1) + UpdateIdsModelessDialog( phDlgModeless, nDlgModelessItem) ; // restituisco risultato return bOk ; } @@ -100,6 +104,9 @@ ExeDeselectObj( int nId) " -- Ok=" + ToString( bOk) ; LOG_INFO( GetCmdLogger(), sLua.c_str()) ; } + // verifico se presente un dialogo modeless per aggiornare i valori + if ( bOk && phDlgModeless != nullptr && nDlgModelessItem != -1) + UpdateIdsModelessDialog( phDlgModeless, nDlgModelessItem) ; // restituisco risultato return bOk ; } @@ -160,6 +167,9 @@ ExeDeselectAll( void) " -- Ok=" + ToString( bOk) ; LOG_INFO( GetCmdLogger(), sLua.c_str()) ; } + // verifico se presente un dialogo modeless per aggiornare i valori + if ( bOk && phDlgModeless != nullptr && nDlgModelessItem != -1) + UpdateIdsModelessDialog( phDlgModeless, nDlgModelessItem) ; // restituisco risultato return bOk ; } @@ -186,6 +196,9 @@ ExeSelectGroupObjs( int nGroupId) " -- Ok=" + ToString( bOk) ; LOG_INFO( GetCmdLogger(), sLua.c_str()) ; } + // verifico se presente un dialogo modeless per aggiornare i valori + if ( bOk && phDlgModeless != nullptr && nDlgModelessItem != -1) + UpdateIdsModelessDialog( phDlgModeless, nDlgModelessItem) ; // restituisco risultato return bOk ; } @@ -209,6 +222,9 @@ ExeDeselectGroupObjs( int nGroupId) " -- Ok=" + ToString( bOk) ; LOG_INFO( GetCmdLogger(), sLua.c_str()) ; } + // verifico se presente un dialogo modeless per aggiornare i valori + if ( bOk && phDlgModeless != nullptr && nDlgModelessItem != -1) + UpdateIdsModelessDialog( phDlgModeless, nDlgModelessItem) ; // restituisco risultato return bOk ; } diff --git a/EgtExecutor.rc b/EgtExecutor.rc index b6a0ccb..856fa02 100644 Binary files a/EgtExecutor.rc and b/EgtExecutor.rc differ diff --git a/EgtExecutor.vcxproj b/EgtExecutor.vcxproj index 0a70be6..afca94f 100644 --- a/EgtExecutor.vcxproj +++ b/EgtExecutor.vcxproj @@ -228,6 +228,7 @@ copy $(TargetPath) \EgtProg\Dll64 + @@ -249,6 +250,7 @@ copy $(TargetPath) \EgtProg\Dll64 + diff --git a/EgtExecutor.vcxproj.filters b/EgtExecutor.vcxproj.filters index e214c45..d88ca92 100644 --- a/EgtExecutor.vcxproj.filters +++ b/EgtExecutor.vcxproj.filters @@ -111,6 +111,9 @@ File di intestazione + + File di intestazione + @@ -431,6 +434,9 @@ File di origine\LUA + + File di origine\Global + diff --git a/LUA_General.cpp b/LUA_General.cpp index 5735f6c..1251bf3 100644 --- a/LUA_General.cpp +++ b/LUA_General.cpp @@ -1,4 +1,4 @@ -//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- // EgalTech 2014-2014 //---------------------------------------------------------------------------- // File : LUA_General.cpp Data : 27.09.14 Versione : 1.5i5 @@ -18,6 +18,7 @@ #include "LUA.h" #include "GenTools.h" #include "resource.h" +#include "AuxDialogBox.h" #include "/EgtDev/Include/ExeExecutor.h" #include "/EgtDev/Include/EGkLuaAux.h" #include "/EgtDev/Include/EGkStringUtils3d.h" @@ -118,7 +119,7 @@ LuaGetKeyPressed( lua_State* L) int nKeyCode ; LuaCheckParam( L, 1, nKeyCode) LuaClearStack( L) ; - // se abilitata UI, controllo se il tasto indicato è premuto + // se abilitata UI, controllo se il tasto indicato è premuto bool bPressed = false ; if ( ExeGetEnableUI()) { bPressed = (( GetAsyncKeyState( nKeyCode) & 0x8000) != 0) ; @@ -181,7 +182,7 @@ LuaSplitString( lua_State* L) LuaClearStack( L) ; // se ricevuta stringa, la divido in parti separate da sSep if ( bFound) { - // eseguo separazione (se tra i separatori c'è spazio ora tokenize collassa eventuali spazi consecutivi in uno solo) + // eseguo separazione (se tra i separatori c'è spazio ora tokenize collassa eventuali spazi consecutivi in uno solo) STRVECTOR vsVal ; Tokenize( sVal, sSep, vsVal) ; // ritorno il risultato @@ -516,7 +517,7 @@ LuaOutLog( lua_State* L) LuaClearStack( L) ; // accodo il messaggio nel file di log ExeOutLog( sOut, nDbgLev) ; - // non c'è risultato + // non c'è risultato return 0 ; } @@ -1178,7 +1179,7 @@ LuaCloseExe( lua_State* L) // eseguo chiusura forzata if ( ExeOnTerminateProcess( nExitCode)) TerminateProcess( GetCurrentProcess(), abs( nExitCode)) ; - // restituisco il risultato (se arrivo qui vuol dire che non è consnetito terminare l'esecuzione) + // restituisco il risultato (se arrivo qui vuol dire che non è consnetito terminare l'esecuzione) LuaSetParam( L, false) ; return 1 ; } @@ -1212,22 +1213,154 @@ LuaReleaseMutex( lua_State* L) } //------------------------------------------------------------------------------- -const int MAX_CTRLS = IDC_TEXT8 -IDC_TEXT1 + 1 ; +// =================================== DIALOG =================================== +// Variabili Globali const int OFFS_CTRLS = 40 ; -enum TYPE_CTRLS { CTRL_NONE = 0, CTRL_CHECK = 1, CTRL_COMBO = 2, CTRL_EDIT = 3, CTRL_COLOR = 4} ; -static int s_nCtrls = 0 ; -static string s_sCaption ; -static string s_sText[MAX_CTRLS] ; -static string s_sEdit[MAX_CTRLS] ; -static int s_nType[MAX_CTRLS] ; +enum TYPE_CTRLS { CTRL_NONE = 0, CTRL_CHECK = 1, CTRL_COMBO = 2, CTRL_EDIT = 3, CTRL_COLOR = 4, CTRL_BUTTON = 5} ; static COLORREF s_CustomColors[16] = {12632256} ; // 16 colori GRAY static bool s_bCustomColorsInit = false ; // flag di inizializzazione colori const char* SEC_SCENE = "Scene" ; const char* KEY_CUSTOMCOLORS = "CustomColors" ; +// Dialogo Modeless +const int MAX_CTRLS = IDC_TEXT8 - IDC_TEXT1 + 1 ; +static int s_nCtrls = 0 ; +static string s_sCaption ; +static string s_sText[MAX_CTRLS] ; +static string s_sEdit[MAX_CTRLS] ; +static int s_nType[MAX_CTRLS] ; +struct DialogContext { + bool bDone = false ; // flag per chiusura dialogo (X/Ok/Cancel) + bool bCancelled = false ; // true se X/Cancel + bool bSelecting = false ; // true quando un BS + in modalità selezione + int nLastBS = -1 ; // indice dell'ultimo BS premuto + STRVECTOR vsResult ; // valori finali da ritornare a Lua +} ; +static string s_sIdSel_ML ; +static bool s_bSelectionMode = false ; // se true allora diventa dialogo "Modeless" +static int s_nActiveRow = - 1 ; // indice della riga di "BS:" attiva + +//------------------------------------------------------------------------------- +// 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 ; + nDlgModelessItem = -1 ; + // il controllo passa al Main + HWND hMain = GetParent( hwndDlg) ; + EnableWindow( hMain, TRUE) ; + DestroyWindow( hwndDlg) ; + phDlgModeless = nullptr ; + 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 ai BS tranne che quello attivo + for ( int i = 0; i < s_nCtrls ; ++ i) { + if (s_sEdit[i].find( "BS:") == 0) { + int idEdit = IDC_EDIT1 + i ; + EnableWindow( GetDlgItem( hDlg, idEdit), ( i == nActiveRow ? TRUE : FALSE)) ; + } + } + + // seleziono solo gli elementi presenti nell'edit corrispondente + nDlgModelessItem = - 1 ; // !<-- Temporaneo [***] + ExeDeselectAll() ; // !<-- NB. avendo messo nDlgModelessItem = -1 il testo non viene rimosso + s_bSelectionMode = true ; + s_nActiveRow = nActiveRow ; + nDlgModelessItem = nEditId ; // !<-- Aggiorno [***] + 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 ; + nDlgModelessItem = -1 ; + + // disabilito tutti gli edit associati ai BS + for ( int i = 0; i < s_nCtrls ; ++ i) { + if ( s_sEdit[i].find( "BS:") == 0) { + int idEdit = IDC_EDIT1 + i ; + EnableWindow( GetDlgItem( hDlg, idEdit), FALSE) ; + SetDlgItemText( hDlg, IDC_BUTTON_SEL1 + i, stringtoW( string{ "-"})) ; + } + } + + // 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() ; +} + //------------------------------------------------------------------------------- BOOL -CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +CALLBACK DialogBoxModelessProc( 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 @@ -1236,6 +1369,14 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam 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 @@ -1259,60 +1400,61 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam 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, + 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) ; + 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) ; + 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) ; + 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) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; + 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 < int( vsVal.size()) ; ++ j) { + for ( int j = 0 ; j < ssize( vsVal) ; ++ j) { string sItem ; if ( vsVal[j][0] == '*') { nSel = j ; @@ -1323,10 +1465,19 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam ComboBox_AddString( hwndCBox, stringtoW( sItem)) ; } ComboBox_SetCurSel( hwndCBox, nSel) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; + 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 ; @@ -1355,28 +1506,28 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam } s_bCustomColorsInit = true ; } - // nascondo qualsiasi tipo di componente ( per sicurezza) - ShowWindow( GetDlgItem( hwndDlg, IDC_EDIT1 + i), SW_HIDE) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_COMBO1 + i), SW_HIDE) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; + // 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 MARGIN_RIGHT = 8 ; // spazio tra preview e bordo destro dialog const int PREVIEW_HEIGHT = 20 ; // altezza del rettangolo const int PREVIEW_MIN_WIDTH = 40 ; // larghezza minima - // ottieni o crea la static preview (allargata fino al bordo destro del dialog) + // creazione della static preview HWND hwndPreview = NULL ; - RECT rcLabel ; - HWND hwndLabel = GetDlgItem( hwndDlg, IDC_TEXT1 + i) ; - RECT rcClient ; GetClientRect( hwndDlg, &rcClient) ; // coordinate client del dialog - if ( hwndLabel && GetWindowRect( hwndLabel, &rcLabel)) { + 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 ptLabel = { rcLabel.right, rcLabel.top } ; - ScreenToClient( hwndDlg, &ptLabel) ; - int x = ptLabel.x + 8 ; // distanza dal label - int y = ptLabel.y ; - int width = rcClient.right - x - MARGIN_RIGHT ; + 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, @@ -1394,22 +1545,42 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam } s_nType[i] = CTRL_COLOR ; } + + // --- se Button di selezione + else if ( s_sEdit[i].find( "BS:") == 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, stringtoW( string{ "-"})) ; // 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, UpdateSelectionModelessDialog, 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) ; - ShowWindow( GetDlgItem( hwndDlg, IDC_CHECK1 + i), SW_HIDE) ; + 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 ; @@ -1425,11 +1596,58 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam } } break ; + case WM_COMMAND : { int id = LOWORD( wParam) ; int code = HIWORD( wParam) ; - // comando di Preview per brush di colori mediante click del mouse - if ( code == STN_CLICKED && id >= IDC_COLOR1 && id <= IDC_COLOR8) { + + // --- 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 ( nDlgModelessItem == nEditId) { + // se selezione attiva, allora deve terminare + if ( s_bSelectionMode) { + SetDlgItemText( hwndDlg, nBtnId, stringtoW( string{ "-"})) ; + EndSelectionMode( hwndDlg) ; + } + // se selezione non attiva, allora viene attivata + else { + SetDlgItemText( hwndDlg, nBtnId, stringtoW( string{ "S"})) ; + StartSelectionMode( hwndDlg, nEditId, nActiveRow) ; + } + } + // se nuovo buttone premuto + else { + // se esisteva una selezione precedente, questa viene annullata + if ( nDlgModelessItem != -1) { + // recupero il Button di selezione precedente e modifico il suo testo + int nPrevBtnId = IDC_BUTTON_SEL1 + ( nDlgModelessItem - IDC_EDIT1) ; + SetDlgItemText( hwndDlg, nPrevBtnId, stringtoW( string{ "-"})) ; + } + // la selezione passa al comando corrente + nDlgModelessItem = nEditId ; + SetDlgItemText( hwndDlg, nBtnId, stringtoW( string{ "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 ( nDlgModelessItem != -1) { + // recupero il Button di selezione precedente e modifico il suo testo + int nPrevBtnId = IDC_BUTTON_SEL1 + ( nDlgModelessItem - IDC_EDIT1) ; + SetDlgItemText( hwndDlg, nPrevBtnId, stringtoW( string{ "-"})) ; + } + EndSelectionMode( hwndDlg) ; + } int idx = id - IDC_COLOR1 ; HWND hwndPreview = GetDlgItem( hwndDlg, IDC_COLOR1 + idx) ; if ( ! hwndPreview) @@ -1458,38 +1676,62 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam InvalidateRect(hwndPreview, NULL, TRUE) ; UpdateWindow( hwndPreview) ; } - return TRUE ; + return TRUE ; // !<-- Esco } switch ( LOWORD( wParam)) { - case IDOK : - 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 ; + case IDOK : { + // recupero il contesto impostato alla creazione della finestra (durante esecuzione WM_INITDIALOG) + DialogContext* ctx = (DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; + if ( ctx != nullptr) { + // inizializzo i risultati + ctx->vsResult.clear() ; + ctx->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 + ctx->vsResult.push_back( s_sEdit[i]) ; } + + ctx->bCancelled = false ; + ctx->bDone = true ; } - [[fallthrough]] ; + + // chiudo il dialogo + OnClosingDialog( hwndDlg) ; + return TRUE ; + } + [[fallthrough]] ; case IDCANCEL : { // pulizia brush colori for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { @@ -1498,16 +1740,96 @@ CALLBACK DialogBoxProc( HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam hColorBrush[i] = NULL ; } } - EndDialog( hwndDlg, wParam) ; + // recupero il contesto impostato alla creazione della finestra ( durante esecuzione WM_INITDIALOG) e se esiste lo rimuovo + DialogContext* ctx = (DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; + if ( ctx != nullptr) { + ctx->bCancelled = true ; + ctx->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 (BS), 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* ctx = (DialogContext*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA) ; + if ( ctx != nullptr) { + ctx->bCancelled = true ; + ctx->bDone = true ; + } + // chiudo il dialogo + OnClosingDialog( hwndDlg) ; + return TRUE ; } } + return FALSE ; } //------------------------------------------------------------------------------- +// 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 +// BS (Button Selection). +// +// 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 BS, 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 BS (mediante Button Selection) 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 BS (Button Selection). Solo la main window viene +// abilitata temporaneamente. +// Questo è fondamentale: disabilitare il dialogo impedirebbe all’utente di poterlo riselezionare dopo la selezione nella scena. +// static int LuaDialogBox( lua_State* L) { @@ -1516,7 +1838,7 @@ LuaDialogBox( lua_State* L) s_nCtrls = 0 ; for ( int i = 0 ; i < MAX_CTRLS ; ++ i) { STRVECTOR vData ; - if ( LuaGetParam( L, 2 + i, vData) && vData.size() >= 2) { + if ( LuaGetParam( L, 2 + i, vData) && ssize( vData) >= 2) { s_sText[i] = Trim( vData[0]) ; s_sEdit[i] = Trim( vData[1]) ; ++ s_nCtrls ; @@ -1525,24 +1847,62 @@ LuaDialogBox( lua_State* L) break ; } LuaClearStack( L) ; - // se abilitata UI, lancio dialogo - if ( ExeGetEnableUI()) { - // lancio dialogo - HWND hTopWnd = ExeGetMainWindowHandle() ; - bool bOk = ( DialogBox( GetModuleIstance(), MAKEINTRESOURCE( IDD_LUADLG), hTopWnd, (DLGPROC)DialogBoxProc) == IDOK) ; - // restituzione parametri - if ( bOk) { - STRVECTOR vRes ; - for ( int i = 0 ; i < s_nCtrls ; ++ i) - vRes.emplace_back( s_sEdit[i]) ; - LuaSetParam( L, vRes) ; - } - else { - LuaSetParam( L) ; + + // se UI disabilitata, esco + if ( ! ExeGetEnableUI()) { + LuaSetParam( L) ; + return 1 ; + } + + // se dialogo Modeless già aperto, errore + if ( phDlgModeless != nullptr) { + LuaSetParam( L) ; + return 1 ; + } + + // creazione del contesto e della Coroutine da gestire + DialogContext* ctx = new DialogContext() ; + if ( ctx == nullptr) { + LuaSetParam( L) ; + return 1 ; + } + + // creazione di un dialogo Modeless + HWND hTopWnd = ExeGetMainWindowHandle() ; + nDlgModelessItem = -1 ; + phDlgModeless = ( CreateDialogParam( GetModuleIstance(), MAKEINTRESOURCE( IDD_LUADLG), hTopWnd, + (DLGPROC)DialogBoxModelessProc, (LPARAM)ctx)) ; + if ( phDlgModeless == nullptr) { + delete( ctx) ; + ctx = nullptr ; + LuaSetParam( L) ; + return 1 ; + } + + // porto la finestra in primo piano + ShowWindow( phDlgModeless, SW_SHOW) ; + SetForegroundWindow( phDlgModeless) ; + + // message Loop Modale (ripresa del Loop come da dialogo modale) + MSG msg ; + while ( ! ctx->bDone && IsWindow( phDlgModeless)) { + if ( GetMessage( &msg, NULL, 0, 0) <= 0) + break ; + if ( ! IsDialogMessage( phDlgModeless, &msg)) { + TranslateMessage( &msg) ; + DispatchMessage( &msg) ; } } + + // ritorno i valori lua + if ( ctx->bCancelled) + LuaSetParam( L, false) ; else - LuaSetParam( L) ; + LuaSetParam( L, ctx->vsResult) ; + + // libero la memoria + delete ctx ; + ctx = nullptr ; return 1 ; } @@ -1607,6 +1967,5 @@ LuaInstallGeneral( LuaMgr& luaMgr) bOk = bOk && luaMgr.RegisterFunction( "EgtCreateMutex", LuaCreateMutex) ; bOk = bOk && luaMgr.RegisterFunction( "EgtReleaseMutex", LuaReleaseMutex) ; bOk = bOk && luaMgr.RegisterFunction( "EgtDialogBox", LuaDialogBox) ; - return bOk ; } diff --git a/Lua_Trimming.cpp b/Lua_Trimming.cpp index dd60487..afb8451 100644 --- a/Lua_Trimming.cpp +++ b/Lua_Trimming.cpp @@ -377,7 +377,7 @@ LuaRegolarizeSurfaceLocally( lua_State* L) LuaCheckParam( L, 5, dLinTol) int nType = 0 ; LuaCheckParam( L, 6, nType) - + int nId = ExeRegolarizeSurfaceLocally( nParentId, nSurfId, nSyncStartId, nSyncEndId, dLinTol, nType) ; LuaSetParam( L, nId) ; return 1 ; diff --git a/resource.h b/resource.h index 8891cf6..7ff80c5 100644 --- a/resource.h +++ b/resource.h @@ -1,6 +1,6 @@ //{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by EgtExecutor.rc +// File di inclusione generato con Microsoft Visual C++. +// Utilizzato da EgtExecutor.rc // #define VS_VERSION_INFO 1 #define IDD_LUADLG 101 @@ -13,6 +13,7 @@ #define IDC_TEXT6 1006 #define IDC_TEXT7 1007 #define IDC_TEXT8 1008 + #define IDC_EDIT1 1011 #define IDC_EDIT2 1012 #define IDC_EDIT3 1013 @@ -21,6 +22,7 @@ #define IDC_EDIT6 1016 #define IDC_EDIT7 1017 #define IDC_EDIT8 1018 + #define IDC_COMBO1 1021 #define IDC_COMBO2 1022 #define IDC_COMBO3 1023 @@ -29,6 +31,7 @@ #define IDC_COMBO6 1026 #define IDC_COMBO7 1027 #define IDC_COMBO8 1028 + #define IDC_CHECK1 1031 #define IDC_CHECK2 1032 #define IDC_CHECK3 1033 @@ -37,6 +40,7 @@ #define IDC_CHECK6 1036 #define IDC_CHECK7 1037 #define IDC_CHECK8 1038 + #define IDC_COLOR1 1041 #define IDC_COLOR2 1042 #define IDC_COLOR3 1043 @@ -45,15 +49,25 @@ #define IDC_COLOR6 1046 #define IDC_COLOR7 1047 #define IDC_COLOR8 1048 + +#define IDC_BUTTON_SEL1 1051 +#define IDC_BUTTON_SEL2 1052 +#define IDC_BUTTON_SEL3 1053 +#define IDC_BUTTON_SEL4 1054 +#define IDC_BUTTON_SEL5 1055 +#define IDC_BUTTON_SEL6 1056 +#define IDC_BUTTON_SEL7 1057 +#define IDC_BUTTON_SEL8 1058 + #define IDC_PICTURE1 1101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_RESOURCE_VALUE 109 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1030 +#define _APS_NEXT_CONTROL_VALUE 1074 #define _APS_NEXT_SYMED_VALUE 115 #endif #endif