/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IceTech Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IceTech Source Code. If not, see . In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "../radiant/qe3.h" #include "DialogDeclBrowser.h" #include "../radiant/InspectorDialog.h" #ifdef ID_DEBUG_MEMORY #undef new #undef DEBUG_NEW #define DEBUG_NEW new #endif const int DECLTYPE_SHIFT = 24; const int DECLINDEX_MASK = ( 1 << DECLTYPE_SHIFT ) - 1; #define GetIdFromTypeAndIndex( type, index ) ( ( (int)type << DECLTYPE_SHIFT ) | index ) #define GetTypeFromId( id ) ( (declType_t) ( (int)id >> DECLTYPE_SHIFT ) ) #define GetIndexFromId( id ) ( (int)id & DECLINDEX_MASK ) static const COLORREF DECL_BROWSER_BG = RGB( 17, 19, 23 ); static const COLORREF DECL_BROWSER_PANEL_BG = RGB( 23, 26, 31 ); static const COLORREF DECL_BROWSER_FIELD_BG = RGB( 14, 16, 20 ); static const COLORREF DECL_BROWSER_TEXT = RGB( 226, 232, 240 ); static const COLORREF DECL_BROWSER_MUTED_TEXT = RGB( 151, 163, 184 ); static const COLORREF DECL_BROWSER_BORDER = RGB( 58, 64, 72 ); static const COLORREF DECL_BROWSER_ACCENT = RGB( 87, 166, 255 ); static const int DECL_BROWSER_MARGIN = 8; static const int DECL_BROWSER_ROW_HEIGHT = 24; static const int DECL_BROWSER_BUTTON_HEIGHT = 24; static const int DECL_BROWSER_STATUS_HEIGHT = 20; static const int DECL_BROWSER_LEFT_MIN_WIDTH = 260; static const int DECL_BROWSER_LEFT_MAX_WIDTH = 420; static const UINT IDC_DECLBROWSER_CHILD_TREE = 0x5C00; static const UINT IDC_DECLBROWSER_CHILD_BASE_TREE = 0x5C01; static const UINT IDC_DECLBROWSER_CHILD_FIND_NAME = 0x5C02; static const UINT IDC_DECLBROWSER_CHILD_FIND_TEXT = 0x5C03; static const UINT IDC_DECLBROWSER_CHILD_FIND = 0x5C04; static const UINT IDC_DECLBROWSER_CHILD_LOAD = 0x5C05; static const UINT IDC_DECLBROWSER_CHILD_RELOAD = 0x5C06; static const UINT IDC_DECLBROWSER_CHILD_NEW_TYPE = 0x5C07; static const UINT IDC_DECLBROWSER_CHILD_NEW_NAME = 0x5C08; static const UINT IDC_DECLBROWSER_CHILD_NEW_FILE = 0x5C09; static const UINT IDC_DECLBROWSER_CHILD_CREATE = 0x5C0A; static const UINT IDC_DECLBROWSER_CHILD_EDITOR = 0x5C0B; static const char *DECL_BROWSER_DARK_FIELD_OLDPROC = "IceTech.DeclBrowser.DarkFieldOldProc"; toolTip_t DialogDeclBrowser::toolTips[] = { { IDC_DECLBROWSER_CHILD_TREE, "declaration browser" }, { IDC_DECLBROWSER_CHILD_FIND_NAME, "search for declarations with matching name, supports *, ? and [abc...]" }, { IDC_DECLBROWSER_CHILD_FIND_TEXT, "search for declarations containing text" }, { IDC_DECLBROWSER_CHILD_FIND, "find declarations matching the search strings" }, { IDC_DECLBROWSER_CHILD_LOAD, "load the selected declaration into the editor" }, { IDC_DECLBROWSER_CHILD_RELOAD, "reload declarations" }, { IDC_DECLBROWSER_CHILD_NEW_TYPE, "type for a new declaration" }, { IDC_DECLBROWSER_CHILD_NEW_NAME, "name for a new declaration" }, { IDC_DECLBROWSER_CHILD_NEW_FILE, "source file for a new declaration" }, { IDC_DECLBROWSER_CHILD_CREATE, "create a new declaration through the declaration manager" }, { 0, NULL } }; static void DeclBrowserFillRect( HDC hDC, const RECT &rc, COLORREF color ) { HBRUSH brush = ::CreateSolidBrush( color ); ::FillRect( hDC, &rc, brush ); ::DeleteObject( brush ); } static void DeclBrowserFrameRect( HDC hDC, const RECT &rc, COLORREF color ) { HBRUSH brush = ::CreateSolidBrush( color ); ::FrameRect( hDC, &rc, brush ); ::DeleteObject( brush ); } static void DeclBrowserApplyNativeDarkTheme( HWND hWnd ) { if ( !hWnd ) { return; } typedef HRESULT (WINAPI *SetWindowThemeProc)( HWND, LPCWSTR, LPCWSTR ); typedef BOOL (WINAPI *AllowDarkModeForWindowProc)( HWND, BOOL ); static HMODULE hUxTheme = ::LoadLibraryA( "uxtheme.dll" ); static SetWindowThemeProc pSetWindowTheme = hUxTheme ? (SetWindowThemeProc)::GetProcAddress( hUxTheme, "SetWindowTheme" ) : NULL; static AllowDarkModeForWindowProc pAllowDarkModeForWindow = hUxTheme ? (AllowDarkModeForWindowProc)::GetProcAddress( hUxTheme, MAKEINTRESOURCEA( 133 ) ) : NULL; if ( pAllowDarkModeForWindow ) { pAllowDarkModeForWindow( hWnd, TRUE ); } if ( pSetWindowTheme ) { pSetWindowTheme( hWnd, L"DarkMode_Explorer", NULL ); } ::SendMessage( hWnd, WM_THEMECHANGED, 0, 0 ); } static bool DeclBrowserIsClass( HWND hWnd, const char *className ) { char actual[128]; actual[0] = '\0'; ::GetClassNameA( hWnd, actual, sizeof( actual ) ); return ::lstrcmpiA( actual, className ) == 0; } static void DeclBrowserRemoveClientEdge( HWND hWnd ) { if ( !hWnd ) { return; } LONG exStyle = ::GetWindowLong( hWnd, GWL_EXSTYLE ); if ( exStyle & WS_EX_CLIENTEDGE ) { ::SetWindowLong( hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE ); ::SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED ); } } static void DeclBrowserDrawWindowFrame( HWND hWnd ) { HDC hDC = ::GetWindowDC( hWnd ); if ( !hDC ) { return; } RECT rc; ::GetWindowRect( hWnd, &rc ); ::OffsetRect( &rc, -rc.left, -rc.top ); DeclBrowserFrameRect( hDC, rc, DECL_BROWSER_BORDER ); ::ReleaseDC( hWnd, hDC ); } static LRESULT CALLBACK DeclBrowserDarkFieldProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { WNDPROC oldProc = (WNDPROC)::GetPropA( hWnd, DECL_BROWSER_DARK_FIELD_OLDPROC ); if ( !oldProc ) { return ::DefWindowProc( hWnd, uMsg, wParam, lParam ); } switch( uMsg ) { case WM_NCPAINT: case WM_PAINT: { LRESULT result = ::CallWindowProc( oldProc, hWnd, uMsg, wParam, lParam ); DeclBrowserDrawWindowFrame( hWnd ); return result; } case WM_SETFOCUS: case WM_KILLFOCUS: case WM_ENABLE: { LRESULT result = ::CallWindowProc( oldProc, hWnd, uMsg, wParam, lParam ); ::RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW ); return result; } case WM_NCDESTROY: { ::SetWindowLongPtr( hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc ); ::RemovePropA( hWnd, DECL_BROWSER_DARK_FIELD_OLDPROC ); return ::CallWindowProc( oldProc, hWnd, uMsg, wParam, lParam ); } } return ::CallWindowProc( oldProc, hWnd, uMsg, wParam, lParam ); } static void DeclBrowserSubclassField( HWND hWnd ) { if ( !hWnd || ::GetPropA( hWnd, DECL_BROWSER_DARK_FIELD_OLDPROC ) ) { return; } WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr( hWnd, GWLP_WNDPROC ); ::SetPropA( hWnd, DECL_BROWSER_DARK_FIELD_OLDPROC, (HANDLE)oldProc ); ::SetWindowLongPtr( hWnd, GWLP_WNDPROC, (LONG_PTR)DeclBrowserDarkFieldProc ); } template< class type > int idListDeclSortCompare( const type *a, const type *b ) { return idStr::IcmpPath( (*a)->GetName(), (*b)->GetName() ); } IMPLEMENT_DYNAMIC(DialogDeclBrowser, CWnd) DialogDeclBrowser::DialogDeclBrowser() : CWnd() , numListedDecls( 0 ) , controlsCreated( false ) , reloadingTree( false ) , m_pchTip( NULL ) , m_pwchTip( NULL ) { } DialogDeclBrowser::~DialogDeclBrowser() { delete m_pwchTip; delete m_pchTip; if ( modernFont.GetSafeHandle() ) { modernFont.DeleteObject(); } if ( bgBrush.GetSafeHandle() ) { bgBrush.DeleteObject(); } if ( panelBrush.GetSafeHandle() ) { panelBrush.DeleteObject(); } if ( editBrush.GetSafeHandle() ) { editBrush.DeleteObject(); } if ( staticBrush.GetSafeHandle() ) { staticBrush.DeleteObject(); } } BOOL DialogDeclBrowser::Create( CWnd *parent, UINT id ) { if ( !parent ) { return FALSE; } if ( id == 0 ) { id = (UINT)-1; } CString className = AfxRegisterWndClass( CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, ::LoadCursor( NULL, IDC_ARROW ), (HBRUSH)::GetStockObject( NULL_BRUSH ), NULL ); CRect rect( 0, 0, 100, 100 ); return CWnd::CreateEx( 0, className, "DeclBrowserChild", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, id ); } BEGIN_MESSAGE_MAP(DialogDeclBrowser, CWnd) ON_WM_CREATE() ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify) ON_WM_CTLCOLOR() ON_WM_ERASEBKGND() ON_WM_PAINT() ON_WM_DESTROY() ON_WM_SIZE() ON_WM_SETFOCUS() ON_NOTIFY(TVN_SELCHANGED, IDC_DECLBROWSER_CHILD_TREE, OnTreeSelChanged) ON_NOTIFY(NM_DBLCLK, IDC_DECLBROWSER_CHILD_TREE, OnTreeDblclk) ON_BN_CLICKED(IDC_DECLBROWSER_CHILD_FIND, OnBnClickedFind) ON_BN_CLICKED(IDC_DECLBROWSER_CHILD_LOAD, OnBnClickedLoad) ON_BN_CLICKED(IDC_DECLBROWSER_CHILD_CREATE, OnBnClickedCreate) ON_BN_CLICKED(IDC_DECLBROWSER_CHILD_RELOAD, OnBnClickedReload) ON_CBN_SELCHANGE(IDC_DECLBROWSER_CHILD_NEW_TYPE, OnTypeChanged) END_MESSAGE_MAP() int DialogDeclBrowser::OnCreate( LPCREATESTRUCT lpCreateStruct ) { if ( CWnd::OnCreate( lpCreateStruct ) == -1 ) { return -1; } CreateChildControls(); EnableToolTips( TRUE ); ApplyDarkTheme(); PopulateTypeCombo(); InitBaseDeclTree(); findNameString = "*"; findTextString = ""; findNameEdit.SetWindowText( findNameString.c_str() ); findTextEdit.SetWindowText( findTextString.c_str() ); OnBnClickedFind(); SetBrowserStatus( va( "%d decls listed", numListedDecls ) ); return 0; } void DialogDeclBrowser::CreateChildControls( void ) { if ( controlsCreated ) { return; } CRect r( 0, 0, 0, 0 ); DWORD staticStyle = WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX; DWORD editStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL; DWORD buttonStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON; declTree.Create( WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS, r, this, IDC_DECLBROWSER_CHILD_TREE ); baseDeclTree.Create( WS_CHILD | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, r, this, IDC_DECLBROWSER_CHILD_BASE_TREE ); findNameStatic.Create( "Name", staticStyle, r, this ); findTextStatic.Create( "Text", staticStyle, r, this ); findNameEdit.CreateEx( WS_EX_CLIENTEDGE, "EDIT", "", editStyle, r, this, IDC_DECLBROWSER_CHILD_FIND_NAME ); findTextEdit.CreateEx( WS_EX_CLIENTEDGE, "EDIT", "", editStyle, r, this, IDC_DECLBROWSER_CHILD_FIND_TEXT ); findButton.Create( "Find", buttonStyle, r, this, IDC_DECLBROWSER_CHILD_FIND ); loadButton.Create( "Load", buttonStyle, r, this, IDC_DECLBROWSER_CHILD_LOAD ); reloadButton.Create( "Reload", buttonStyle, r, this, IDC_DECLBROWSER_CHILD_RELOAD ); newTypeStatic.Create( "Type", staticStyle, r, this ); newNameStatic.Create( "New", staticStyle, r, this ); newFileStatic.Create( "File", staticStyle, r, this ); newTypeCombo.Create( WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, r, this, IDC_DECLBROWSER_CHILD_NEW_TYPE ); newNameEdit.CreateEx( WS_EX_CLIENTEDGE, "EDIT", "", editStyle, r, this, IDC_DECLBROWSER_CHILD_NEW_NAME ); newFileEdit.CreateEx( WS_EX_CLIENTEDGE, "EDIT", "", editStyle, r, this, IDC_DECLBROWSER_CHILD_NEW_FILE ); createButton.Create( "Create", buttonStyle, r, this, IDC_DECLBROWSER_CHILD_CREATE ); statusText.Create( "No declaration selected", WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX, r, this ); declEditor.Create( this, IDC_DECLBROWSER_CHILD_EDITOR ); controlsCreated = true; } void DialogDeclBrowser::PopulateTypeCombo( void ) { if ( !newTypeCombo.GetSafeHwnd() ) { return; } newTypeCombo.ResetContent(); for( int i = 0; i < declManager->GetNumDeclTypes(); i++ ) { newTypeCombo.AddString( declManager->GetDeclNameFromType( (declType_t)i ) ); } if ( newTypeCombo.GetCount() > 0 ) { newTypeCombo.SetCurSel( 0 ); OnTypeChanged(); } } void DialogDeclBrowser::ApplyModernFontRecursive( CWnd *wnd ) { if ( !wnd || !wnd->GetSafeHwnd() || !modernFont.GetSafeHandle() ) { return; } wnd->SetFont( &modernFont, FALSE ); CWnd *child = wnd->GetWindow( GW_CHILD ); while( child ) { child->SetFont( &modernFont, FALSE ); child = child->GetNextWindow(); } } void DialogDeclBrowser::ApplyDarkTheme( void ) { if ( !GetSafeHwnd() ) { return; } if ( !modernFont.GetSafeHandle() ) { LOGFONT lf; memset( &lf, 0, sizeof( lf ) ); HDC hDC = ::GetDC( GetSafeHwnd() ); const int dpiY = GetDeviceCaps( hDC, LOGPIXELSY ); ::ReleaseDC( GetSafeHwnd(), hDC ); lf.lfHeight = -MulDiv( 9, dpiY, 72 ); lf.lfWeight = FW_NORMAL; lf.lfQuality = CLEARTYPE_QUALITY; strncpy( lf.lfFaceName, "Segoe UI", sizeof( lf.lfFaceName ) - 1 ); modernFont.CreateFontIndirect( &lf ); } if ( !bgBrush.GetSafeHandle() ) { bgBrush.CreateSolidBrush( DECL_BROWSER_BG ); } if ( !panelBrush.GetSafeHandle() ) { panelBrush.CreateSolidBrush( DECL_BROWSER_PANEL_BG ); } if ( !editBrush.GetSafeHandle() ) { editBrush.CreateSolidBrush( DECL_BROWSER_FIELD_BG ); } if ( !staticBrush.GetSafeHandle() ) { staticBrush.CreateSolidBrush( DECL_BROWSER_BG ); } SetFont( &modernFont, FALSE ); ApplyModernFontRecursive( this ); DeclBrowserApplyNativeDarkTheme( GetSafeHwnd() ); ModifyStyleEx( WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED ); for( HWND child = ::GetWindow( GetSafeHwnd(), GW_CHILD ); child != NULL; child = ::GetWindow( child, GW_HWNDNEXT ) ) { DeclBrowserApplyNativeDarkTheme( child ); if ( DeclBrowserIsClass( child, "Edit" ) || DeclBrowserIsClass( child, "SysTreeView32" ) || DeclBrowserIsClass( child, "ComboBox" ) ) { DeclBrowserRemoveClientEdge( child ); DeclBrowserSubclassField( child ); } } if ( declTree.GetSafeHwnd() ) { ::SendMessage( declTree.GetSafeHwnd(), TVM_SETBKCOLOR, 0, (LPARAM)DECL_BROWSER_FIELD_BG ); ::SendMessage( declTree.GetSafeHwnd(), TVM_SETTEXTCOLOR, 0, (LPARAM)DECL_BROWSER_TEXT ); } Invalidate( TRUE ); } BOOL DialogDeclBrowser::OnToolTipNotify( UINT id, NMHDR *pNMHDR, LRESULT *pResult ) { if ( pNMHDR->hwndFrom == declTree.GetSafeHwnd() ) { CString toolTip; const idDecl *decl = GetDeclFromTreeItem( (HTREEITEM)pNMHDR->idFrom ); if ( !decl ) { return FALSE; } toolTip = va( "%s, line: %d", decl->GetFileName(), decl->GetLineNum() ); TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; #ifndef _UNICODE if( pNMHDR->code == TTN_NEEDTEXTA ) { delete m_pchTip; m_pchTip = new char[toolTip.GetLength() + 2]; lstrcpyn( m_pchTip, toolTip, toolTip.GetLength() + 1 ); pTTTA->lpszText = (LPSTR)m_pchTip; } else { delete m_pwchTip; m_pwchTip = new WCHAR[toolTip.GetLength() + 2]; _mbstowcsz( m_pwchTip, toolTip, toolTip.GetLength() + 1 ); pTTTW->lpszText = (WCHAR*)m_pwchTip; } #else if( pNMHDR->code == TTN_NEEDTEXTA ) { delete m_pchTip; m_pchTip = new char[toolTip.GetLength() + 2]; _wcstombsz( m_pchTip, toolTip, toolTip.GetLength() + 1 ); pTTTA->lpszText = (LPSTR)m_pchTip; } else { delete m_pwchTip; m_pwchTip = new WCHAR[toolTip.GetLength() + 2]; lstrcpyn( m_pwchTip, toolTip, toolTip.GetLength() + 1 ); pTTTW->lpszText = (WCHAR*)m_pwchTip; } #endif return TRUE; } return DefaultOnToolTipNotify( toolTips, id, pNMHDR, pResult ); } HBRUSH DialogDeclBrowser::OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor ) { HBRUSH hbr = CWnd::OnCtlColor( pDC, pWnd, nCtlColor ); if ( !pDC ) { return hbr; } pDC->SetTextColor( DECL_BROWSER_TEXT ); switch( nCtlColor ) { case CTLCOLOR_DLG: pDC->SetBkColor( DECL_BROWSER_BG ); return (HBRUSH)bgBrush.GetSafeHandle(); case CTLCOLOR_STATIC: pDC->SetBkColor( DECL_BROWSER_BG ); pDC->SetTextColor( DECL_BROWSER_MUTED_TEXT ); return (HBRUSH)staticBrush.GetSafeHandle(); case CTLCOLOR_EDIT: case CTLCOLOR_LISTBOX: pDC->SetBkColor( DECL_BROWSER_FIELD_BG ); pDC->SetTextColor( DECL_BROWSER_TEXT ); return (HBRUSH)editBrush.GetSafeHandle(); case CTLCOLOR_BTN: pDC->SetBkColor( DECL_BROWSER_BG ); pDC->SetTextColor( DECL_BROWSER_TEXT ); return (HBRUSH)bgBrush.GetSafeHandle(); default: break; } return hbr; } BOOL DialogDeclBrowser::OnEraseBkgnd( CDC* pDC ) { CRect rc; GetClientRect( &rc ); pDC->FillSolidRect( &rc, DECL_BROWSER_BG ); return TRUE; } void DialogDeclBrowser::OnPaint() { CPaintDC dc( this ); CRect rc; GetClientRect( &rc ); dc.FillSolidRect( &rc, DECL_BROWSER_BG ); } void DialogDeclBrowser::OnSetFocus( CWnd *pOldWnd ) { CWnd::OnSetFocus( pOldWnd ); if ( declTree.GetSafeHwnd() ) { declTree.SetFocus(); } } void DialogDeclBrowser::OnDestroy() { CWnd::OnDestroy(); } void DialogDeclBrowser::OnSize( UINT nType, int cx, int cy ) { CWnd::OnSize( nType, cx, cy ); LayoutBrowser( cx, cy ); } void DialogDeclBrowser::LayoutBrowser( int cx, int cy ) { if ( !controlsCreated || cx <= 0 || cy <= 0 ) { return; } const int margin = DECL_BROWSER_MARGIN; const int row = DECL_BROWSER_ROW_HEIGHT; const int buttonHeight = DECL_BROWSER_BUTTON_HEIGHT; const int statusHeight = DECL_BROWSER_STATUS_HEIGHT; int leftWidth = cx / 3; if ( leftWidth < DECL_BROWSER_LEFT_MIN_WIDTH ) { leftWidth = DECL_BROWSER_LEFT_MIN_WIDTH; } if ( leftWidth > DECL_BROWSER_LEFT_MAX_WIDTH ) { leftWidth = DECL_BROWSER_LEFT_MAX_WIDTH; } if ( leftWidth > cx - 240 ) { leftWidth = cx - 240; } if ( leftWidth < 180 ) { leftWidth = 180; } const int left = margin; const int top = margin; const int divider = left + leftWidth + margin; const int editorLeft = divider + margin; const int statusTop = cy - statusHeight; const int createHeight = row * 4 + margin * 2; const int searchHeight = row * 3 + margin; const int treeTop = top + searchHeight; const int createTop = statusTop - createHeight - margin; const int treeBottom = createTop - margin; LockWindowUpdate(); if ( findNameStatic.GetSafeHwnd() ) { findNameStatic.MoveWindow( left, top + 3, 42, row ); } if ( findNameEdit.GetSafeHwnd() ) { findNameEdit.MoveWindow( left + 46, top, leftWidth - 46, row ); } if ( findTextStatic.GetSafeHwnd() ) { findTextStatic.MoveWindow( left, top + row + 3, 42, row ); } if ( findTextEdit.GetSafeHwnd() ) { findTextEdit.MoveWindow( left + 46, top + row, leftWidth - 46, row ); } if ( findButton.GetSafeHwnd() ) { findButton.MoveWindow( left, top + row * 2 + margin / 2, 64, buttonHeight ); } if ( loadButton.GetSafeHwnd() ) { loadButton.MoveWindow( left + 68, top + row * 2 + margin / 2, 64, buttonHeight ); } if ( reloadButton.GetSafeHwnd() ) { reloadButton.MoveWindow( left + 136, top + row * 2 + margin / 2, 72, buttonHeight ); } if ( declTree.GetSafeHwnd() ) { declTree.MoveWindow( left, treeTop, leftWidth, treeBottom - treeTop ); } if ( baseDeclTree.GetSafeHwnd() ) { baseDeclTree.MoveWindow( 0, 0, 0, 0 ); } if ( newTypeStatic.GetSafeHwnd() ) { newTypeStatic.MoveWindow( left, createTop + 3, 40, row ); } if ( newTypeCombo.GetSafeHwnd() ) { newTypeCombo.MoveWindow( left + 46, createTop, leftWidth - 46, 300 ); } if ( newNameStatic.GetSafeHwnd() ) { newNameStatic.MoveWindow( left, createTop + row + 3, 40, row ); } if ( newNameEdit.GetSafeHwnd() ) { newNameEdit.MoveWindow( left + 46, createTop + row, leftWidth - 46, row ); } if ( newFileStatic.GetSafeHwnd() ) { newFileStatic.MoveWindow( left, createTop + row * 2 + 3, 40, row ); } if ( newFileEdit.GetSafeHwnd() ) { newFileEdit.MoveWindow( left + 46, createTop + row * 2, leftWidth - 46, row ); } if ( createButton.GetSafeHwnd() ) { createButton.MoveWindow( left, createTop + row * 3 + margin / 2, 82, buttonHeight ); } if ( declEditor.GetSafeHwnd() ) { declEditor.MoveWindow( editorLeft, top, cx - editorLeft - margin, statusTop - top - margin ); } if ( statusText.GetSafeHwnd() ) { statusText.MoveWindow( margin, statusTop + 2, cx - margin * 2, statusHeight - 2 ); } UnlockWindowUpdate(); } void DialogDeclBrowser::AddDeclTypeToTree( declType_t type, const char *root, CPathTreeCtrl &tree ) { int i; idList decls; idPathTreeStack stack; idStr rootStr, declName; decls.SetNum( declManager->GetNumDecls( type ) ); for ( i = 0; i < decls.Num(); i++ ) { decls[i] = declManager->DeclByIndex( type, i, false ); } decls.Sort( idListDeclSortCompare ); rootStr = root; rootStr += "/"; stack.PushRoot( NULL ); for ( i = 0; i < decls.Num(); i++ ) { if ( !decls[i] ) { continue; } declName = rootStr + decls[i]->GetName(); declName.BackSlashesToSlashes(); declName.Strip( ' ' ); tree.AddPathToTree( declName, GetIdFromTypeAndIndex( type, decls[i]->Index() ), stack ); } } void DialogDeclBrowser::InitBaseDeclTree( void ) { if ( !baseDeclTree.GetSafeHwnd() ) { return; } numListedDecls = 0; baseDeclTree.DeleteAllItems(); for ( int i = 0; i < declManager->GetNumDeclTypes(); i++ ) { AddDeclTypeToTree( (declType_t)i, declManager->GetDeclNameFromType( (declType_t)i ), baseDeclTree ); } } void DialogDeclBrowser::GetDeclName( HTREEITEM item, idStr &typeName, idStr &declName ) const { HTREEITEM parent; idStr itemName; declName.Clear(); if ( !item ) { typeName.Clear(); return; } for( parent = declTree.GetParentItem( item ); parent; parent = declTree.GetParentItem( parent ) ) { itemName = declTree.GetItemText( item ); declName = itemName + "/" + declName; item = parent; } declName.Strip( '/' ); typeName = declTree.GetItemText( item ); } const idDecl *DialogDeclBrowser::GetDeclFromTreeItem( HTREEITEM item ) const { int id, index; declType_t type; if ( !item || declTree.GetChildItem( item ) ) { return NULL; } id = declTree.GetItemData( item ); type = GetTypeFromId( id ); index = GetIndexFromId( id ); if ( type < 0 || type >= declManager->GetNumDeclTypes() ) { return NULL; } return declManager->DeclByIndex( type, index, false ); } const idDecl *DialogDeclBrowser::GetSelectedDecl( void ) const { return GetDeclFromTreeItem( declTree.GetSelectedItem() ); } bool DialogDeclBrowser::CompareDecl( HTREEITEM item, const char *name ) const { if ( findNameString.Length() ) { if ( !idStr::Filter( findNameString, name, false ) ) { return false; } } if ( findTextString.Length() ) { int id, index; declType_t type; id = declTree.GetItemData( item ); type = GetTypeFromId( id ); index = GetIndexFromId( id ); if ( type < 0 || type >= declManager->GetNumDeclTypes() ) { return false; } const idDecl *decl = declManager->DeclByIndex( type, index, false ); if ( !decl ) { return false; } char *declText = (char *)_alloca( ( decl->GetTextLength() + 1 ) * sizeof( char ) ); decl->GetText( declText ); if ( idStr::FindText( declText, findTextString, false ) == -1 ) { return false; } } return true; } bool DeclBrowserCompareDecl( void *data, HTREEITEM item, const char *name ) { return reinterpret_cast( data )->CompareDecl( item, name ); } void DialogDeclBrowser::OnBnClickedFind() { CString windowText; findNameEdit.GetWindowText( windowText ); findNameString = windowText; findNameString.Strip( ' ' ); if ( !findNameString.Length() ) { findNameString = "*"; } findTextEdit.GetWindowText( windowText ); findTextString = windowText; findTextString.Strip( ' ' ); reloadingTree = true; numListedDecls = baseDeclTree.SearchTree( DeclBrowserCompareDecl, this, declTree ); reloadingTree = false; SetBrowserStatus( va( "%d decls listed", numListedDecls ) ); } void DialogDeclBrowser::OnBnClickedLoad() { LoadSelectedDecl(); } void DialogDeclBrowser::LoadSelectedDecl( void ) { const idDecl *selected = GetSelectedDecl(); if ( !selected ) { declEditor.ClearDecl(); SetBrowserStatus( va( "%d decls listed", numListedDecls ) ); return; } idDecl *editable = const_cast( selected ); declEditor.LoadDecl( editable ); SetCreateDefaultsFromSelection( selected ); SetBrowserStatus( va( "%d decls listed - %s/%s, %s line %d", numListedDecls, declManager->GetDeclNameFromType( selected->GetType() ), selected->GetName(), selected->GetFileName(), selected->GetLineNum() ) ); } void DialogDeclBrowser::OnTreeSelChanged( NMHDR* pNMHDR, LRESULT* pResult ) { if ( !reloadingTree ) { const idDecl *decl = GetSelectedDecl(); if ( decl ) { findNameEdit.SetWindowText( va( "%s/%s", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName() ) ); LoadSelectedDecl(); } else { HTREEITEM item = declTree.GetSelectedItem(); idStr typeName, declName; GetDeclName( item, typeName, declName ); if ( typeName.Length() ) { findNameEdit.SetWindowText( va( "%s/%s*", typeName.c_str(), declName.c_str() ) ); } SetBrowserStatus( va( "%d decls listed", numListedDecls ) ); } } *pResult = 0; } void DialogDeclBrowser::OnTreeDblclk( NMHDR *pNMHDR, LRESULT *pResult ) { LoadSelectedDecl(); FocusDeclEditor(); *pResult = 1; } void DialogDeclBrowser::OnBnClickedReload() { declManager->Reload( false ); ReloadDeclarations(); } void DialogDeclBrowser::ReloadDeclarations( void ) { idStr selectedType; idStr selectedName; const idDecl *selected = GetSelectedDecl(); if ( selected ) { selectedType = declManager->GetDeclNameFromType( selected->GetType() ); selectedName = selected->GetName(); } InitBaseDeclTree(); OnBnClickedFind(); if ( selectedType.Length() && selectedName.Length() ) { declType_t type = declManager->GetDeclTypeFromName( selectedType ); SelectDeclInTree( type, selectedName ); } } void DialogDeclBrowser::SelectDeclInTree( declType_t type, const char *name ) { if ( !name || !name[0] || type < 0 || type >= declManager->GetNumDeclTypes() ) { return; } idStr path = declManager->GetDeclNameFromType( type ); path += "/"; path += name; HTREEITEM item = declTree.FindItem( path ); if ( item ) { declTree.SelectItem( item ); declTree.EnsureVisible( item ); LoadSelectedDecl(); } } void DialogDeclBrowser::FocusDeclEditor( void ) { if ( declEditor.GetSafeHwnd() ) { declEditor.SetFocus(); } } declType_t DialogDeclBrowser::GetSelectedNewType( void ) { CString typeName; int sel = newTypeCombo.GetCurSel(); if ( sel != CB_ERR ) { newTypeCombo.GetLBText( sel, typeName ); } else { newTypeCombo.GetWindowText( typeName ); } return declManager->GetDeclTypeFromName( typeName ); } void DialogDeclBrowser::GetDefaultFileForType( declType_t type, idStr &fileName ) const { switch( type ) { case DECL_TABLE: case DECL_MATERIAL: fileName = "materials/generated.mtr"; break; case DECL_SKIN: fileName = "skins/generated.skin"; break; case DECL_SOUND: fileName = "sound/generated.sndshd"; break; case DECL_ENTITYDEF: fileName = "def/generated.def"; break; case DECL_MODELDEF: fileName = "def/generated.def"; break; case DECL_FX: fileName = "fx/generated.fx"; break; case DECL_PARTICLE: fileName = "particles/generated.prt"; break; case DECL_AF: fileName = "af/generated.af"; break; default: fileName = "def/generated.decl"; break; } } void DialogDeclBrowser::SetCreateDefaultsFromSelection( const idDecl *decl ) { if ( !decl ) { return; } if ( newTypeCombo.GetSafeHwnd() ) { CString typeName = declManager->GetDeclNameFromType( decl->GetType() ); int index = newTypeCombo.FindStringExact( -1, typeName ); if ( index != CB_ERR ) { newTypeCombo.SetCurSel( index ); } } if ( newFileEdit.GetSafeHwnd() ) { newFileEdit.SetWindowText( decl->GetFileName() ); } } void DialogDeclBrowser::OnTypeChanged() { declType_t type = GetSelectedNewType(); idStr fileName; GetDefaultFileForType( type, fileName ); if ( newFileEdit.GetSafeHwnd() ) { newFileEdit.SetWindowText( fileName.c_str() ); } } void DialogDeclBrowser::OnBnClickedCreate() { CString nameText; CString fileText; newNameEdit.GetWindowText( nameText ); newFileEdit.GetWindowText( fileText ); nameText.TrimLeft(); nameText.TrimRight(); fileText.TrimLeft(); fileText.TrimRight(); if ( nameText.IsEmpty() ) { MessageBox( "Enter a declaration name.", "Create declaration", MB_OK | MB_ICONINFORMATION ); return; } declType_t type = GetSelectedNewType(); if ( type < 0 || type >= declManager->GetNumDeclTypes() ) { MessageBox( "Select a valid declaration type.", "Create declaration", MB_OK | MB_ICONERROR ); return; } idStr fileName; if ( fileText.IsEmpty() ) { GetDefaultFileForType( type, fileName ); } else { fileName = fileText; } idStr path = declManager->GetDeclNameFromType( type ); path += "/"; path += (LPCTSTR)nameText; if ( baseDeclTree.FindItem( path ) ) { MessageBox( "Declaration already exists.", "Create declaration", MB_OK | MB_ICONERROR ); return; } idDecl *newDecl = declManager->CreateNewDecl( type, nameText, fileName, true ); if ( !newDecl ) { MessageBox( "The declaration manager could not create the declaration.", "Create declaration", MB_OK | MB_ICONERROR ); return; } findNameEdit.SetWindowText( va( "%s/%s", declManager->GetDeclNameFromType( type ), newDecl->GetName() ) ); findTextEdit.SetWindowText( "" ); InitBaseDeclTree(); OnBnClickedFind(); SelectDeclInTree( type, newDecl->GetName() ); FocusDeclEditor(); } void DialogDeclBrowser::SetBrowserStatus( const char *text ) { if ( statusText.GetSafeHwnd() ) { statusText.SetWindowText( text ? text : "" ); } } void DeclBrowserInit( const idDict *spawnArgs ) { if ( renderSystem->IsFullScreen() ) { common->Printf( "Cannot run the declaration editor in fullscreen mode.\n" "Set r_fullscreen to 0 and vid_restart.\n" ); return; } InitAfx(); idKeyInput::ClearStates(); if ( g_Inspectors && g_Inspectors->GetSafeHwnd() ) { g_Inspectors->ShowWindow( SW_SHOW ); g_Inspectors->SetMode( W_DECLS ); g_Inspectors->GetDeclBrowser().SetFocus(); return; } common->Printf( "Declaration editor is hosted in the Inspector Decls tab. Open the Inspector window first.\n" ); } void DeclBrowserRun( void ) { // No private message pump is needed. The declaration browser/editor now // lives inside the Inspector dialog and uses Radiant's normal UI pump. } void DeclBrowserShutdown( void ) { // Owned by CInspectorDialog. } void DeclBrowserReloadDeclarations( void ) { if ( g_Inspectors && g_Inspectors->GetSafeHwnd() ) { g_Inspectors->GetDeclBrowser().ReloadDeclarations(); } }