// cdxCDynamicControlsManager.cpp: implementation of the cdxCDynamicControlsManager class. // ////////////////////////////////////////////////////////////////////// /* * you should define OEMRESOURCE * in your project settings (C/C++, General) ! */ #include "stdafx.h" #include "cdxCDynamicControlsManager.h" #include #include #ifndef OBM_SIZE #define OBM_SIZE 32766 #pragma message("*** NOTE: cdxCDynamicControlsManager.cpp: Please define OEMRESOURCE in your project settings !") // taken from WinresRc.h // if not used for any reason #endif #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Some static variables ///////////////////////////////////////////////////////////////////////////// #define REGVAL_NOSTATE -1 #define REGVAL_VISIBLE 1 #define REGVAL_HIDDEN 0 #define REGVAL_MAXIMIZED 1 #define REGVAL_ICONIC 0 #define REGVAL_INVALID 0 #define REGVAL_VALID 1 /* * registry value names * (for StoreWindowPosition()/RestoreWindowPosition()) */ static LPCTSTR lpszRegVal_Left = _T("Left"), lpszRegVal_Right = _T("Right"), lpszRegVal_Top = _T("Top"), lpszRegVal_Bottom = _T("Bottom"), lpszRegVal_Visible = _T("Visibility"), lpszRegVal_State = _T("State"), lpszRegVal_Valid = _T("(valid)"); ///////////////////////////////////////////////////////////////////////////// // cdxCDynamicControlsManager::ControlData::ControlEntry ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // construction/destruction ///////////////////////////////////////////////////////////////////////////// cdxCDynamicControlsManager::ControlData::ControlEntry::ControlEntry(CWnd & ctrl, ControlData & rMaster) : m_rMaster(rMaster), m_rCtrl(ctrl) { if(m_pNext = rMaster.m_pCtrl) m_pNext->m_pPrev = this; rMaster.m_pCtrl = this; m_pPrev = NULL; // raise total counter ++rMaster.m_rMaster.m_iTotalCnt; } cdxCDynamicControlsManager::ControlData::ControlEntry::~ControlEntry() { if(m_pPrev) { if(m_pPrev->m_pNext = m_pNext) m_pNext->m_pPrev = m_pPrev; } else { ASSERT( m_rMaster.m_pCtrl == this ); if(m_rMaster.m_pCtrl = m_pNext) m_pNext->m_pPrev = NULL; } // lower ASSERT( m_rMaster.m_rMaster.m_iTotalCnt > 0 ); ++m_rMaster.m_rMaster.m_iTotalCnt; } ///////////////////////////////////////////////////////////////////////////// // cdxCDynamicControlsManager::ControlData ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // construction/destruction ///////////////////////////////////////////////////////////////////////////// /* * constructor * copies all paramaters and gets the controls initial position using * GetWindowPos(). * * NOTE that the constructor need ctrl.m_hWnd to exist (in contrast to * Add() */ cdxCDynamicControlsManager::ControlData::ControlData(cdxCDynamicControlsManager & rMaster, CWnd & ctrl, const PositionSetup & rPosSetup) : m_rMaster(rMaster), m_pCtrl(NULL), m_pNext(NULL), m_pPrev(NULL), m_posSetup(rPosSetup) { ASSERT(::IsWindow(ctrl.m_hWnd)); // control must have already been created ! ASSERT(rPosSetup.IsValid()); // // get initial values // WINDOWPLACEMENT wpl; VERIFY( ctrl.GetWindowPlacement(&wpl) ); m_rectOriginal = wpl.rcNormalPosition; // // remember control // new ControlEntry(ctrl,*this); ASSERT(m_pCtrl != NULL); // // link us to the cdxCDynamicControlsManager's list // if(m_pNext = m_rMaster.m_pFirst) m_pNext->m_pPrev = this; m_pPrev = NULL; m_rMaster.m_pFirst = this; } /* * detach from list * The m_Ctrl deletes all children by itself */ cdxCDynamicControlsManager::ControlData::~ControlData() { // // delete all control references // while(m_pCtrl) delete m_pCtrl; // // unlink from list // if(m_pPrev) { if(m_pPrev->m_pNext = m_pNext) m_pNext->m_pPrev = m_pPrev; } else { ASSERT(m_rMaster.m_pFirst == this); if(m_rMaster.m_pFirst = m_pNext) m_pNext->m_pPrev = NULL; } } ///////////////////////////////////////////////////////////////////////////// // cdxCDynamicControlsManager::ControlData virtuals ///////////////////////////////////////////////////////////////////////////// /* * checks whether the CWnd is part of this control data */ bool cdxCDynamicControlsManager::ControlData::IsMember(CWnd & ctrl) const { for(const ControlEntry *pEntry = m_pCtrl; pEntry; pEntry = pEntry->GetNext()) if(*pEntry == ctrl) return true; return false; } /* * removes a CWnd from this chain */ bool cdxCDynamicControlsManager::ControlData::Rem(CWnd & ctrl) { for(ControlEntry *pEntry = m_pCtrl; pEntry; pEntry = pEntry->GetNext()) if(*pEntry == ctrl) { delete pEntry; return true; } return false; } /* * Get current position */ CRect cdxCDynamicControlsManager::ControlData::GetCurrentPosition() const { if(!IsUsed()) { ASSERT(false); // all sub-controls have been deleted return CRect(0,0,0,0); } WINDOWPLACEMENT wpl; VERIFY( m_pCtrl->GetCWnd().GetWindowPlacement(&wpl) ); return CRect(wpl.rcNormalPosition); } /* * modify initial setup * NOTE: this function does not move the controls. * You habe to call cdxCDynamicControlsManager::ReorganizeControls past * using this function */ bool cdxCDynamicControlsManager::ControlData::Modify(const CRect & rectOriginal, const PositionSetup & rSetup) { if((rectOriginal.left > rectOriginal.right) || (rectOriginal.top > rectOriginal.bottom) || !rSetup.IsValid()) { ASSERT(false); // bad function call return false; } m_rectOriginal = rectOriginal; m_posSetup = rSetup; return true; } ///////////////////////////////////////////////////////////////////////////// // cdxCDynamicControlsManager ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // handling events from CWnd ///////////////////////////////////////////////////////////////////////////// /* * this function initializes the following members: * m_pWnd - the window handle * m_szCurrent - the current window's size * m_szMin - the minimum window's size (taken from current size) * m_szMax - the maximum window's size (set to (0,0) <=> don't change maximum) * m_wndSizeIcon - the icon (if wanted) * * parameters: * rWnd - the window to supervise * fd - in which directions can we size the window (does only apply to user-sizing) * bSizeIcon - do you want a sizable icon ? * pBaseClientSize- if non-zero, this defines the real (normal) size of the * window relative to all furher calculations will be made. * if zero, the current window's size will be taken. */ void cdxCDynamicControlsManager::DoInitWindow(CWnd & rWnd, Freedom fd, bool bSizeIcon, const CSize * pBaseClientSize) { ASSERT(m_pWnd == NULL); // you MUST NOT call this function twice ! ASSERT(::IsWindow(rWnd.m_hWnd)); // rWnd MUST already exist !! m_pWnd = &rWnd; m_Freedom = fd; // // get current's window size // CRect rect; m_pWnd->GetWindowRect(&rect); CRect rectClient; m_pWnd->GetClientRect(&rectClient); if(!pBaseClientSize) { m_szClientRelative.cx = rectClient.Width(); m_szClientRelative.cy = rectClient.Height(); m_szMin.cx = rect.Width(); m_szMin.cy = rect.Height(); } else { ASSERT((pBaseClientSize->cx > 0) && (pBaseClientSize->cy > 0)); m_szClientRelative = *pBaseClientSize; m_szMin.cx = m_szClientRelative.cx + (rect.Width() - rectClient.Width()); m_szMin.cy = m_szClientRelative.cy + (rect.Height() - rectClient.Height()); } m_szMax.cx = 0; m_szMax.cy = 0; // // set up icon if wanted // if(bSizeIcon) { VERIFY( m_pWndSizeIcon = new cdxCSizeIconCtrl ); VERIFY( m_pWndSizeIcon->Create(m_pWnd,m_idSizeIcon) ); // creates my control; id is SIZE_CONTROL_ID AddSzControl(*m_pWndSizeIcon,mdRepos,mdRepos); m_pWndSizeIcon->ShowWindow(SW_SHOW); // finally - show it } } ///////////////////////////////////////////////////////////////////////////// /* * fill in MINMAXINFO as requested * Call your CWnd's OnGetMinMaxInfo first ! * * changed due to a but report by Michel Wassink */ void cdxCDynamicControlsManager::DoOnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) { if(IsReady() && !IsDisabled()) { lpMMI->ptMinTrackSize.x = m_szMin.cx; lpMMI->ptMinTrackSize.y = m_szMin.cy; if(m_Freedom & fdHoriz) { if(m_szMax.cx) lpMMI->ptMaxTrackSize.x = m_szMax.cx; } else lpMMI->ptMaxTrackSize.x = m_szMin.cx; if(m_Freedom & fdVert) { if(m_szMax.cy) lpMMI->ptMaxTrackSize.y = m_szMax.cy; } else lpMMI->ptMaxTrackSize.y = m_szMin.cy; } } /* * handle OnSize - we can't rely on cx,cy being client dimensions * as stated in the documentation ... */ void cdxCDynamicControlsManager::DoOnSize(UINT nType, int cx, int cy) { if(IsReady() && (nType != SIZE_MINIMIZED) && !IsDisabled()) ReorganizeControls(true); } /* * free all memory * after having called this function, you can reuse the object * although I wouldn't recommend to do so :) */ void cdxCDynamicControlsManager::DoDestroyWindow() { while(m_pFirst) { OnDeleteControlPosition(*m_pFirst); delete m_pFirst; } if(m_pWndSizeIcon) { if(::IsWindow(m_pWndSizeIcon->m_hWnd)) m_pWndSizeIcon->DestroyWindow(); delete m_pWndSizeIcon; m_pWndSizeIcon = NULL; } m_pWnd = NULL; } ///////////////////////////////////////////////////////////////////////////// // control positioning ///////////////////////////////////////////////////////////////////////////// /* * Reposition (without current rectangle size) * * rectWin - window rectangle (including border) * rectClient - window rectangle (client area) * Note that since release 6, rectClient.left and .top might be > zero * (for scrolling) * bRedraw - invalidate & update window */ void cdxCDynamicControlsManager::ReorganizeControlsAdvanced(const CRect & rectWin, CRect rectClient, bool bRedraw) { ASSERT(IsReady()); if(!GetTotalChildCnt()) return; // // we won't go smaller with the whole window than // m_szMin // if(rectWin.Width() < m_szMin.cx) rectClient.right += (m_szMin.cx - rectWin.Width()); if(rectWin.Height() < m_szMin.cy) rectClient.bottom += (m_szMin.cy - rectWin.Height()); // // we new replace all controls // CSize szDelta; szDelta.cx = rectClient.Width() - m_szClientRelative.cx; szDelta.cy = rectClient.Height() - m_szClientRelative.cy; CPoint pntOffset = rectClient.TopLeft(); // newly added code by Rodger Bernstein AFX_SIZEPARENTPARAMS layout; ControlData *sz; bool bManual = true; if(!( layout.hDWP = ::BeginDeferWindowPos(GetTotalChildCnt()) )) { TRACE(_T("*** ERROR[cdxCDynamicControlsManager::ReorganizeControlsAdvanced()]: BeginDeferWindowPos() failed.\n")); } else { for(sz = m_pFirst; sz; sz = sz->GetNext()) sz->OnSize(szDelta, &layout, &pntOffset); if(!::EndDeferWindowPos(layout.hDWP)) { TRACE(_T("*** ERROR[cdxCDynamicControlsManager::ReorganizeControlsAdvanced()]: EndDeferWindowPos() failed.\n")); } else bManual = false; } if(bManual) for(sz = m_pFirst; sz; sz = sz->GetNext()) sz->OnSize(szDelta, NULL); if(bRedraw && m_pWnd->IsWindowVisible()) { m_pWnd->RedrawWindow(NULL, NULL, RDW_UPDATENOW | RDW_NOERASE); } } ///////////////////////////////////////////////////////////////////////////// // misc ///////////////////////////////////////////////////////////////////////////// /* * change minimum and maximum height of window. * * szMin - new minimum size (use GetMinSize() to leave it as being before) * szMax - new maximum size ( " GetMaxSize() ") * Set to CSize(0,0) if you don't want a maximum size. * bResizeIfNecessary - call FixWindowSize() past calculating new sizes. * * returns false if szMin and szMax are illegal (e.g. szMin > szMax) */ bool cdxCDynamicControlsManager::SetMinMaxSize(const CSize & szMin, const CSize & szMax, bool bResizeIfNecessary) { ASSERT(IsReady()); // DoInitWindow() not called ? if((szMax.cx && (szMin.cx > szMax.cx)) || (szMax.cy && (szMin.cy > szMax.cy))) { return false; } m_szMin = szMin; m_szMax = szMax; if(bResizeIfNecessary) FixWindowSize(); return true; } /* * this function ensure that the window's size is between m_szMin and m_szMax. * returns true if window size has been changed */ bool cdxCDynamicControlsManager::FixWindowSize() { ASSERT(IsReady()); // use DoInitWindow() first ! CSize szCurrent = GetWindowSize(*m_pWnd), szDelta; if(m_szMax.cx && (szCurrent.cx > m_szMax.cx)) szDelta.cx = m_szMax.cx - szCurrent.cx; // is negative else if(szCurrent.cx < m_szMin.cx) szDelta.cx = m_szMin.cx - szCurrent.cx; // is positive else szDelta.cx = 0; if(m_szMax.cy && (szCurrent.cy > m_szMax.cy)) szDelta.cy = m_szMax.cy - szCurrent.cy; // is negative else if(szCurrent.cy < m_szMin.cy) szDelta.cy = m_szMin.cy - szCurrent.cy; // is positive else szDelta.cy = 0; if(!szDelta.cx && !szDelta.cy) return false; // nothing to do StretchWindow(*m_pWnd,szDelta); return true; } ///////////////////////////////////////////////////////////////////////////// /* * hide and show icon */ void cdxCDynamicControlsManager::HideSizeIcon() { if(m_pWndSizeIcon && ::IsWindow(m_pWndSizeIcon->m_hWnd)) m_pWndSizeIcon->ShowWindow(SW_HIDE); } void cdxCDynamicControlsManager::ShowSizeIcon() { if(m_pWndSizeIcon && ::IsWindow(m_pWndSizeIcon->m_hWnd)) m_pWndSizeIcon->ShowWindow(SW_SHOW); } ///////////////////////////////////////////////////////////////////////////// // static functions: window sizing ///////////////////////////////////////////////////////////////////////////// /* * stretches the window by szDelta (i.e. if szDelta is 100, the window is enlarged by 100 pixels) * stretching means that the center point of the window remains * * returns false if the window would be smaller than (1,1) * * NOTE: this function does NOT care of the min/max dimensions of a window * Use MoveWindow() if you need to take care of it. * * STATIC */ bool cdxCDynamicControlsManager::StretchWindow(CWnd & rWnd, const CSize & szDelta) { ASSERT(::IsWindow(rWnd.m_hWnd)); WINDOWPLACEMENT wpl; rWnd.GetWindowPlacement(&wpl); wpl.rcNormalPosition.left -= szDelta.cx / 2; wpl.rcNormalPosition.right += (szDelta.cx + 1) / 2; wpl.rcNormalPosition.top -= szDelta.cy / 2; wpl.rcNormalPosition.bottom += (szDelta.cy + 1) / 2; // wpl.flags = SW_SHOWNA|SW_SHOWNOACTIVATE; if((wpl.rcNormalPosition.left >= wpl.rcNormalPosition.right) || (wpl.rcNormalPosition.top >= wpl.rcNormalPosition.bottom)) return false; VERIFY( rWnd.SetWindowPos( NULL, wpl.rcNormalPosition.left, wpl.rcNormalPosition.top, wpl.rcNormalPosition.right - wpl.rcNormalPosition.left, wpl.rcNormalPosition.bottom - wpl.rcNormalPosition.top, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER) ); return true; } /* * stretch window by a percent value * the algorithm calculates the new size for both dimensions by: * * newWid = oldWid + (oldWid * iAddPcnt) / 100 * * NOTE: iAddPcnt may even be nagtive, but it MUST be greater than -100. * NOTE: this function does NOT care of the min/max dimensions of a window * * The function will return false if the new size would be empty. * * STATIC */ bool cdxCDynamicControlsManager::StretchWindow(CWnd & rWnd, int iAddPcnt) { ASSERT(::IsWindow(rWnd.m_hWnd)); CSize szDelta = GetWindowSize(rWnd); szDelta.cx = (szDelta.cx * iAddPcnt) / 100; szDelta.cy = (szDelta.cy * iAddPcnt) / 100; return StretchWindow(rWnd,szDelta); } /* * get current window's size * * STATIC */ CSize cdxCDynamicControlsManager::GetWindowSize(CWnd & rWnd) { ASSERT(::IsWindow(rWnd.m_hWnd)); CRect rect; rWnd.GetWindowRect(&rect); return CSize(rect.Width(),rect.Height()); } ///////////////////////////////////////////////////////////////////////////// // static functions: window & registry ///////////////////////////////////////////////////////////////////////////// /* * stores a window's position and visiblity to the registry. * * return false if any error occured * * STATIC */ bool cdxCDynamicControlsManager::StoreWindowPosition(CWnd & rWnd, LPCTSTR lpszProfile) { ASSERT(::IsWindow(rWnd.m_hWnd) && lpszProfile && *lpszProfile); // can't use an empty profile section string; see CWinApp::GetProfileInt() for further information WINDOWPLACEMENT wpl; VERIFY( rWnd.GetWindowPlacement(&wpl) ); BOOL bVisible = rWnd.IsWindowVisible(); int iState = REGVAL_NOSTATE; if(rWnd.IsIconic()) iState = REGVAL_ICONIC; else if(rWnd.IsZoomed()) iState = REGVAL_MAXIMIZED; return AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Valid, REGVAL_INVALID) && // invalidate first AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Left, wpl.rcNormalPosition.left) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Right, wpl.rcNormalPosition.right) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Top, wpl.rcNormalPosition.top) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Bottom, wpl.rcNormalPosition.bottom) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Visible, bVisible ? REGVAL_VISIBLE : REGVAL_HIDDEN) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_State, iState) && AfxGetApp()->WriteProfileInt(lpszProfile, lpszRegVal_Valid, REGVAL_VALID); // validate position } /* * load the registry data stored by StoreWindowPosition() * returns true if data have been found in the registry * * STATIC */ bool cdxCDynamicControlsManager::RestoreWindowPosition(CWnd & rWnd, LPCTSTR lpszProfile, UINT restoreFlags) { ASSERT(::IsWindow(rWnd.m_hWnd) && lpszProfile && *lpszProfile); // can't use an empty profile section string; see CWinApp::GetProfileInt() for further information // // first, we check whether the position had been saved successful any time before // if( AfxGetApp()->GetProfileInt(lpszProfile,lpszRegVal_Valid,REGVAL_INVALID) != REGVAL_VALID ) return false; // // get old position // WINDOWPLACEMENT wpl; VERIFY( rWnd.GetWindowPlacement(&wpl) ); // // read registry // int iState = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_State, REGVAL_NOSTATE); // // get window's previous normal position // wpl.rcNormalPosition.left = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_Left, wpl.rcNormalPosition.left); wpl.rcNormalPosition.right = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_Right, wpl.rcNormalPosition.right); wpl.rcNormalPosition.top = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_Top, wpl.rcNormalPosition.top); wpl.rcNormalPosition.bottom = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_Bottom, wpl.rcNormalPosition.bottom); if(wpl.rcNormalPosition.left > wpl.rcNormalPosition.right) { long l = wpl.rcNormalPosition.right; wpl.rcNormalPosition.right = wpl.rcNormalPosition.left; wpl.rcNormalPosition.left = l; } if(wpl.rcNormalPosition.top > wpl.rcNormalPosition.bottom) { long l = wpl.rcNormalPosition.bottom; wpl.rcNormalPosition.bottom = wpl.rcNormalPosition.top; wpl.rcNormalPosition.top = l; } // // get restore stuff // UINT showCmd = SW_SHOWNA; if(restoreFlags & rflg_state) { if(iState == REGVAL_MAXIMIZED) showCmd = SW_MAXIMIZE; else if(iState == REGVAL_ICONIC) showCmd = SW_MINIMIZE; } // // use MoveWindow() which takes care of WM_GETMINMAXINFO // rWnd.MoveWindow( wpl.rcNormalPosition.left,wpl.rcNormalPosition.top, wpl.rcNormalPosition.right - wpl.rcNormalPosition.left, wpl.rcNormalPosition.bottom - wpl.rcNormalPosition.top, showCmd == SW_SHOWNA); if(showCmd != SW_SHOWNA) { // read updated position VERIFY( rWnd.GetWindowPlacement(&wpl) ); wpl.showCmd = showCmd; rWnd.SetWindowPlacement(&wpl); } // // get visiblity // if(restoreFlags & rflg_visibility) { int i = AfxGetApp()->GetProfileInt(lpszProfile, lpszRegVal_Visible, REGVAL_NOSTATE); if(i == REGVAL_VISIBLE) rWnd.ShowWindow(SW_SHOW); else if(i == REGVAL_HIDDEN) rWnd.ShowWindow(SW_HIDE); } return true; }