/* =========================================================================== 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 "QE3.H" #include "TabsDlg.h" /* ======================== Tabs dialog top caption Draws a non-layout-changing caption at the top of the tab dialog. This is an overlay child window, so it does not move the tab control, docked children, or floating/docking logic. The caption text follows the currently selected tab. ======================== */ static const char* TABSDLG_TOP_CAPTION_CLASS = "QER_TabsDlgTopCaption"; static const char* TABSDLG_TOP_CAPTION_STATE_PROP = "QER_TabsDlgTopCaptionState"; static const char* TABSDLG_TAB_NOTIFY_STATE_PROP = "QER_TabsDlgTabNotifyState"; static const int TABSDLG_TOP_CAPTION_HEIGHT = 28; static const int TABSDLG_TOP_CAPTION_CHILD_ID = 64010; struct tabsDlgTopCaptionState_t { WNDPROC oldProc; HWND captionWnd; }; struct tabsDlgTabNotifyState_t { WNDPROC oldProc; HWND parentWnd; }; static LRESULT CALLBACK TabsDlg_TopCaptionProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK TabsDlg_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK TabsDlg_TabNotifyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static WNDPROC TabsDlg_GetWindowProc(HWND hWnd) { #ifdef _WIN64 return (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); #else return (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC); #endif } static void TabsDlg_SetWindowProc(HWND hWnd, WNDPROC proc) { #ifdef _WIN64 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); #else SetWindowLong(hWnd, GWL_WNDPROC, (LONG)proc); #endif } static COLORREF TabsDlg_LerpColor(COLORREF a, COLORREF b, float t) { const int ar = GetRValue(a); const int ag = GetGValue(a); const int ab = GetBValue(a); const int br = GetRValue(b); const int bg = GetGValue(b); const int bb = GetBValue(b); const int r = ar + (int)((br - ar) * t); const int g = ag + (int)((bg - ag) * t); const int bl = ab + (int)((bb - ab) * t); return RGB(r, g, bl); } static void TabsDlg_FillHorizontalGradient(HDC hDC, const RECT& rect, COLORREF leftColor, COLORREF rightColor) { const int width = rect.right - rect.left; if (width <= 0) { return; } for (int x = 0; x < width; x++) { const float t = (width > 1) ? ((float)x / (float)(width - 1)) : 0.0f; const COLORREF color = TabsDlg_LerpColor(leftColor, rightColor, t); HPEN pen = CreatePen(PS_SOLID, 1, color); HPEN oldPen = (HPEN)SelectObject(hDC, pen); MoveToEx(hDC, rect.left + x, rect.top, NULL); LineTo(hDC, rect.left + x, rect.bottom); SelectObject(hDC, oldPen); DeleteObject(pen); } } static tabsDlgTopCaptionState_t* TabsDlg_GetTopCaptionState(HWND hWnd) { return (tabsDlgTopCaptionState_t*)GetProp(hWnd, TABSDLG_TOP_CAPTION_STATE_PROP); } static tabsDlgTabNotifyState_t* TabsDlg_GetTabNotifyState(HWND hTabs) { return (tabsDlgTabNotifyState_t*)GetProp(hTabs, TABSDLG_TAB_NOTIFY_STATE_PROP); } static HWND TabsDlg_GetTabControl(HWND hWnd) { HWND hTabs = GetDlgItem(hWnd, IDC_TAB_INSPECTOR); return IsWindow(hTabs) ? hTabs : NULL; } static CString TabsDlg_GetCurrentTopCaption(HWND hWnd) { HWND hTabs = TabsDlg_GetTabControl(hWnd); if (!hTabs) { return "Viewport"; } int curSel = TabCtrl_GetCurSel(hTabs); if (curSel < 0 && TabCtrl_GetItemCount(hTabs) > 0) { curSel = 0; } if (curSel >= 0) { char text[256]; text[0] = '\0'; TCITEM item; memset(&item, 0, sizeof(item)); item.mask = TCIF_TEXT; item.pszText = text; item.cchTextMax = sizeof(text); if (TabCtrl_GetItem(hTabs, curSel, &item) && text[0] != '\0') { return text; } } return "Viewport"; } static void TabsDlg_RegisterTopCaptionClass() { static bool registered = false; if (registered) { return; } WNDCLASS wc; memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = TabsDlg_TopCaptionProc; wc.hInstance = AfxGetInstanceHandle(); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszClassName = TABSDLG_TOP_CAPTION_CLASS; AfxRegisterClass(&wc); registered = true; } static void TabsDlg_DrawTopCaption(HWND hCaption, HDC hDC) { HWND hParent = GetParent(hCaption); RECT rect; GetClientRect(hCaption, &rect); TabsDlg_FillHorizontalGradient( hDC, rect, RGB(24, 82, 150), RGB(92, 96, 104) ); RECT dividerRect = rect; dividerRect.top = rect.bottom - 1; dividerRect.bottom = rect.bottom; FillRect(hDC, ÷rRect, (HBRUSH)(COLOR_3DSHADOW + 1)); HFONT font = (HFONT)GetStockObject(DEFAULT_GUI_FONT); HFONT oldFont = NULL; if (font) { oldFont = (HFONT)SelectObject(hDC, font); } SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, RGB(235, 242, 255)); RECT textRect = rect; textRect.left += 8; textRect.right -= 8; CString caption = TabsDlg_GetCurrentTopCaption(hParent); DrawText( hDC, caption, -1, &textRect, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS ); if (oldFont) { SelectObject(hDC, oldFont); } } static LRESULT CALLBACK TabsDlg_TopCaptionProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_ERASEBKGND: return 1; case WM_SETCURSOR: SetCursor(LoadCursor(NULL, IDC_ARROW)); return TRUE; case WM_PAINT: { PAINTSTRUCT ps; HDC hDC = BeginPaint(hWnd, &ps); TabsDlg_DrawTopCaption(hWnd, hDC); EndPaint(hWnd, &ps); return 0; } } return DefWindowProc(hWnd, uMsg, wParam, lParam); } static void TabsDlg_LayoutTopCaption(HWND hWnd) { tabsDlgTopCaptionState_t* state = TabsDlg_GetTopCaptionState(hWnd); if (!state || !IsWindow(state->captionWnd)) { return; } RECT clientRect; GetClientRect(hWnd, &clientRect); int width = clientRect.right - clientRect.left; if (width < 1) { width = 1; } SetWindowPos( state->captionWnd, HWND_TOP, 0, 0, width, TABSDLG_TOP_CAPTION_HEIGHT, SWP_NOACTIVATE | SWP_SHOWWINDOW ); } static void TabsDlg_RefreshTopCaption(HWND hWnd) { tabsDlgTopCaptionState_t* state = TabsDlg_GetTopCaptionState(hWnd); if (!state || !IsWindow(state->captionWnd)) { return; } TabsDlg_LayoutTopCaption(hWnd); InvalidateRect(state->captionWnd, NULL, FALSE); UpdateWindow(state->captionWnd); } static void TabsDlg_UninstallTabNotify(HWND hTabs) { tabsDlgTabNotifyState_t* state = TabsDlg_GetTabNotifyState(hTabs); if (!state) { return; } TabsDlg_SetWindowProc(hTabs, state->oldProc); RemoveProp(hTabs, TABSDLG_TAB_NOTIFY_STATE_PROP); delete state; } static LRESULT CALLBACK TabsDlg_TabNotifyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { tabsDlgTabNotifyState_t* state = TabsDlg_GetTabNotifyState(hWnd); WNDPROC oldProc = state ? state->oldProc : NULL; HWND parentWnd = state ? state->parentWnd : GetParent(hWnd); switch (uMsg) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_KEYDOWN: case WM_KEYUP: case TCM_SETCURSEL: case TCM_SETCURFOCUS: { LRESULT result = oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); TabsDlg_RefreshTopCaption(parentWnd); return result; } case WM_NCDESTROY: { TabsDlg_UninstallTabNotify(hWnd); return oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); } } return oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); } static void TabsDlg_InstallTabNotify(HWND hWnd) { HWND hTabs = TabsDlg_GetTabControl(hWnd); if (!hTabs || TabsDlg_GetTabNotifyState(hTabs)) { return; } tabsDlgTabNotifyState_t* state = new tabsDlgTabNotifyState_t; state->oldProc = TabsDlg_GetWindowProc(hTabs); state->parentWnd = hWnd; SetProp(hTabs, TABSDLG_TAB_NOTIFY_STATE_PROP, (HANDLE)state); TabsDlg_SetWindowProc(hTabs, TabsDlg_TabNotifyProc); } static LRESULT CALLBACK TabsDlg_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { tabsDlgTopCaptionState_t* state = TabsDlg_GetTopCaptionState(hWnd); WNDPROC oldProc = state ? state->oldProc : NULL; switch (uMsg) { case WM_SIZE: case WM_WINDOWPOSCHANGED: { LRESULT result = oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); TabsDlg_LayoutTopCaption(hWnd); return result; } case WM_NCDESTROY: { HWND hTabs = TabsDlg_GetTabControl(hWnd); if (hTabs) { TabsDlg_UninstallTabNotify(hTabs); } HWND hCaption = state ? state->captionWnd : NULL; if (IsWindow(hCaption)) { DestroyWindow(hCaption); } if (oldProc) { TabsDlg_SetWindowProc(hWnd, oldProc); } RemoveProp(hWnd, TABSDLG_TOP_CAPTION_STATE_PROP); if (state) { delete state; } return oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); } } return oldProc ? CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam) : DefWindowProc(hWnd, uMsg, wParam, lParam); } static void TabsDlg_InstallTopCaption(HWND hWnd) { if (!IsWindow(hWnd)) { return; } tabsDlgTopCaptionState_t* state = TabsDlg_GetTopCaptionState(hWnd); if (!state) { TabsDlg_RegisterTopCaptionClass(); state = new tabsDlgTopCaptionState_t; memset(state, 0, sizeof(*state)); state->oldProc = TabsDlg_GetWindowProc(hWnd); SetProp(hWnd, TABSDLG_TOP_CAPTION_STATE_PROP, (HANDLE)state); TabsDlg_SetWindowProc(hWnd, TabsDlg_ParentProc); } if (!IsWindow(state->captionWnd)) { state->captionWnd = CreateWindowEx( 0, TABSDLG_TOP_CAPTION_CLASS, "", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, 1, TABSDLG_TOP_CAPTION_HEIGHT, hWnd, (HMENU)TABSDLG_TOP_CAPTION_CHILD_ID, AfxGetInstanceHandle(), NULL); } TabsDlg_InstallTabNotify(hWnd); TabsDlg_LayoutTopCaption(hWnd); TabsDlg_RefreshTopCaption(hWnd); } // CTabsDlg dialog //IMPLEMENT_DYNAMIC ( CTabsDlg , CDialog ) CTabsDlg::CTabsDlg(UINT ID, CWnd* pParent /*=NULL*/) : CDialog(ID, pParent) { m_DragTabActive = false; } BEGIN_MESSAGE_MAP(CTabsDlg, CDialog) //}}AFX_MSG_MAP // ON_NOTIFY(TCN_SELCHANGE, IDC_TAB1, OnTcnSelchangeTab1) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_WM_DESTROY() END_MESSAGE_MAP() void CTabsDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_TAB_INSPECTOR, m_Tabs); } // CTabsDlg message handlers BOOL CTabsDlg::OnInitDialog() { CDialog::OnInitDialog(); TabsDlg_InstallTopCaption(GetSafeHwnd()); return TRUE; // return TRUE unless you set the focus to a control } void CTabsDlg::OnTcnSelchange(NMHDR* pNMHDR, LRESULT* pResult) { int ID = TabCtrl_GetCurSel(pNMHDR->hwndFrom); if (ID >= 0) { TCITEM item; item.mask = TCIF_PARAM; ShowAllWindows(FALSE); TabCtrl_GetItem(m_Tabs.GetSafeHwnd(), ID, &item); DockedWindowInfo* info = (DockedWindowInfo*)item.lParam; ASSERT(info); info->m_TabControlIndex = ID; info->m_Window->ShowWindow(TRUE); TabsDlg_RefreshTopCaption(GetSafeHwnd()); } } void CTabsDlg::DockWindow(int ID, bool dock) { DockedWindowInfo* info = NULL; m_Windows.Lookup((WORD)ID, (void*&)info); ASSERT(info); ASSERT(m_Tabs.GetSafeHwnd()); ShowAllWindows(FALSE); if (!dock) { //make a containing window and assign the dialog to it CRect rect; CString classname = AfxRegisterWndClass(CS_DBLCLKS, 0, 0, 0); info->m_State = DockedWindowInfo::FLOATING; info->m_Window->GetWindowRect(rect); info->m_Container.CreateEx(WS_EX_TOOLWINDOW, classname, info->m_Title, WS_THICKFRAME | WS_SYSMENU | WS_POPUP | WS_CAPTION, rect, this, 0); info->m_Window->SetParent(&info->m_Container); info->m_Window->ShowWindow(TRUE); info->m_Container.SetDockManager(this); info->m_Container.ShowWindow(TRUE); info->m_Container.SetDialog(info->m_Window, info->m_ID); if (info->m_TabControlIndex >= 0) { m_Tabs.DeleteItem(info->m_TabControlIndex); } if (m_Tabs.GetItemCount() > 0) { m_Tabs.SetCurFocus(0); } CString placementName = info->m_Title + "Placement"; LoadWindowPlacement(info->m_Container, placementName); } else { info->m_State = DockedWindowInfo::DOCKED; info->m_TabControlIndex = m_Tabs.InsertItem(TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM, 0, info->m_Title, info->m_ImageID, (LPARAM)info); info->m_Window->SetParent(this); info->m_Window->ShowWindow(TRUE); info->m_Container.SetDockManager(NULL); //so it doesn't try to call back and redock this window info->m_Container.DestroyWindow(); CRect rect; GetWindowRect(rect); //stupid hack to get the window reitself properly rect.DeflateRect(0, 0, 0, 1); MoveWindow(rect); rect.InflateRect(0, 0, 0, 1); MoveWindow(rect); } UpdateTabControlIndices(); FocusWindow(ID); TabsDlg_InstallTopCaption(GetSafeHwnd()); TabsDlg_RefreshTopCaption(GetSafeHwnd()); if (info->m_DockCallback) { info->m_DockCallback(dock, info->m_ID, info->m_Window); } SaveWindowPlacement(); } int CTabsDlg::PreTranslateMessage(MSG* msg) { if (msg->message == WM_LBUTTONDBLCLK && msg->hwnd == m_Tabs.GetSafeHwnd()) { HandleUndock(); return TRUE; } //steal lbutton clicks for the main dialog too, but let the tabs do their default thing as well if (msg->message == WM_LBUTTONDOWN && msg->hwnd == m_Tabs.GetSafeHwnd()) { m_Tabs.SendMessage(msg->message, msg->wParam, msg->lParam); m_DragTabActive = true; TabsDlg_RefreshTopCaption(GetSafeHwnd()); } else if (msg->message == WM_LBUTTONUP && msg->hwnd == m_Tabs.GetSafeHwnd()) { m_Tabs.SendMessage(msg->message, msg->wParam, msg->lParam); m_DragTabActive = false; TabsDlg_RefreshTopCaption(GetSafeHwnd()); } return CDialog::PreTranslateMessage(msg); } bool CTabsDlg::RectWithinDockManager(CRect& rect) { CRect tabsRect, intersectionRect; m_Tabs.GetWindowRect(tabsRect); intersectionRect.IntersectRect(tabsRect, rect); return !(intersectionRect.IsRectEmpty()); } void CTabsDlg::OnLButtonDown(UINT nFlags, CPoint point) { CDialog::OnLButtonDown(nFlags, point); } void CTabsDlg::OnLButtonUp(UINT nFlags, CPoint point) { if (m_DragTabActive && ((abs(point.x - m_DragDownPoint.x) > 50) || (abs(point.y - m_DragDownPoint.y) > 50))) { HandleUndock(); m_DragTabActive = false; } CDialog::OnLButtonUp(nFlags, point); } void CTabsDlg::HandleUndock() { TCITEM item; item.mask = TCIF_PARAM; int curSel = TabCtrl_GetCurSel(m_Tabs.GetSafeHwnd()); TabCtrl_GetItem(m_Tabs.GetSafeHwnd(), curSel, &item); DockedWindowInfo* info = (DockedWindowInfo*)item.lParam; ASSERT(info); DockWindow(info->m_ID, false); } void CTabsDlg::OnMouseMove(UINT nFlags, CPoint point) { CDialog::OnMouseMove(nFlags, point); } void CTabsDlg::AddDockedWindow(CWnd* wnd, int ID, int imageID, const CString& title, bool dock, pfnOnDockEvent dockCallback) { DockedWindowInfo* info = NULL; m_Windows.Lookup((WORD)ID, (void*&)info); ASSERT(wnd); ASSERT(info == NULL); dock = true; info = new DockedWindowInfo(wnd, ID, imageID, title, dockCallback); m_Windows.SetAt((WORD)ID, info); DockWindow(ID, dock); UpdateTabControlIndices(); TabsDlg_RefreshTopCaption(GetSafeHwnd()); } void CTabsDlg::ShowAllWindows(bool show) { POSITION pos; WORD ID; DockedWindowInfo* info = NULL; for (pos = m_Windows.GetStartPosition(); pos != NULL; ) { m_Windows.GetNextAssoc(pos, ID, (void*&)info); ASSERT(info->m_Window); if (info->m_State == DockedWindowInfo::DOCKED) { info->m_Window->ShowWindow(show); } } } void CTabsDlg::FocusWindow(int ID) { DockedWindowInfo* info = NULL; m_Windows.Lookup((WORD)ID, (void*&)info); ASSERT(info); ASSERT(info->m_Window); if (info->m_State == DockedWindowInfo::DOCKED) { TabCtrl_SetCurFocus(m_Tabs.GetSafeHwnd(), info->m_TabControlIndex); } else { info->m_Container.SetFocus(); } TabsDlg_RefreshTopCaption(GetSafeHwnd()); } void CTabsDlg::UpdateTabControlIndices() { TCITEM item; item.mask = TCIF_PARAM; DockedWindowInfo* info = NULL; int itemCount = m_Tabs.GetItemCount(); for (int i = 0; i < itemCount; i++) { if (!m_Tabs.GetItem(i, &item)) { Sys_Error("UpdateTabControlIndices(): GetItem failed!\n"); } info = (DockedWindowInfo*)item.lParam; info->m_TabControlIndex = i; } } void CTabsDlg::OnDestroy() { TCITEM item; item.mask = TCIF_PARAM; DockedWindowInfo* info = NULL; for (int i = 0; i < m_Tabs.GetItemCount(); i++) { m_Tabs.GetItem(i, &item); info = (DockedWindowInfo*)item.lParam; ASSERT(info); delete info; } CDialog::OnDestroy(); } bool CTabsDlg::IsDocked(CWnd* wnd) { bool docked = false; DockedWindowInfo* info = NULL; CString placementName; POSITION pos; WORD wID; for (pos = m_Windows.GetStartPosition(); pos != NULL; ) { m_Windows.GetNextAssoc(pos, wID, (void*&)info); if (info->m_Window == wnd) { docked = (info->m_State == DockedWindowInfo::DOCKED); break; } } return docked; } void CTabsDlg::SaveWindowPlacement(int ID) { DockedWindowInfo* info = NULL; CString placementName; POSITION pos; WORD wID = ID; for (pos = m_Windows.GetStartPosition(); pos != NULL; ) { m_Windows.GetNextAssoc(pos, wID, (void*&)info); if ((info->m_State == DockedWindowInfo::FLOATING) && ((ID == -1) || (ID == info->m_ID))) { placementName = info->m_Title + "Placement"; ::SaveWindowPlacement(info->m_Container.GetSafeHwnd(), placementName); } } }