/*
===========================================================================
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"
#include
#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";
#ifndef TVS_NOTOOLTIPS
#define TVS_NOTOOLTIPS 0x0080
#endif
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();
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 | TVS_NOTOOLTIPS, r, this, IDC_DECLBROWSER_CHILD_TREE );
baseDeclTree.Create( WS_CHILD | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_NOTOOLTIPS, 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" ) ) {
DeclBrowserRemoveClientEdge( child );
} else if ( DeclBrowserIsClass( child, "SysTreeView32" ) || DeclBrowserIsClass( child, "ComboBox" ) ) {
// Keep tree/combo controls native-subclass free; the tree owns its tooltip path.
DeclBrowserRemoveClientEdge( 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 ( pResult ) {
*pResult = 0;
}
// CPathTreeCtrl already owns its per-item tooltip forwarding. Calling
// TreeView APIs from inside the forwarded TTN_NEEDTEXT path can re-enter
// comctl32 and hang the docked inspector, so the docked browser only uses
// static control tooltips here.
if ( pNMHDR && pNMHDR->hwndFrom == declTree.GetSafeHwnd() ) {
return FALSE;
}
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 {
if ( !item || !declTree.GetSafeHwnd() ) {
return NULL;
}
TVITEM tvItem;
memset( &tvItem, 0, sizeof( tvItem ) );
tvItem.mask = TVIF_PARAM | TVIF_CHILDREN;
tvItem.hItem = item;
if ( !::SendMessage( declTree.GetSafeHwnd(), TVM_GETITEM, 0, (LPARAM)&tvItem ) ) {
return NULL;
}
if ( tvItem.cChildren != 0 ) {
return NULL;
}
const int id = (int)tvItem.lParam;
declType_t type = GetTypeFromId( id );
const int index = GetIndexFromId( id );
if ( type < 0 || type >= declManager->GetNumDeclTypes() || index < 0 || index >= declManager->GetNumDecls( type ) ) {
return NULL;
}
const idDecl *decl = declManager->DeclByIndex( type, index, false );
if ( !decl || decl->GetType() != type || decl->Index() != index ) {
return NULL;
}
return decl;
}
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() ) {
TVITEM tvItem;
memset( &tvItem, 0, sizeof( tvItem ) );
tvItem.mask = TVIF_PARAM | TVIF_CHILDREN;
tvItem.hItem = item;
if ( !baseDeclTree.GetSafeHwnd() || !::SendMessage( baseDeclTree.GetSafeHwnd(), TVM_GETITEM, 0, (LPARAM)&tvItem ) ) {
return false;
}
if ( tvItem.cChildren != 0 ) {
return false;
}
const int id = (int)tvItem.lParam;
declType_t type = GetTypeFromId( id );
const int index = GetIndexFromId( id );
if ( type < 0 || type >= declManager->GetNumDeclTypes() || index < 0 || index >= declManager->GetNumDecls( type ) ) {
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();
}
}