///////////////////////////////////////////////////////////////////////// // // Project: ItsyBitsy window support module // Module: itsybits.c // // // ItsyBitsy is a support module that allows you to create windows // that look and act very much like a popup window witha system // menu and caption bar, except everything is scaled to about 2/3 // scale. // // For documentation on how to use ItsyBits, read the document // ITSYBITS.DOC. // // Revisions: // 9/27/91 Charlie Kindel (cek/ckindel) // Wrote and documented it. // // 1/14/93 cek // 2/23/93 cek Added minimize/maximize buttons. // 3/18/93 cek Fixed system menu bug where system menu // popped back up if you clicked on the // icon again while it was up. // 3/24/93 cek More comments. Fixed DS_MODALDIALOG style // problem. Use auto precompiled headers // in MSVC. // ////////////////////////////////////////////////////////////////////////// #include #include #include #include #include "itsybits.h" // CAPTIONXY is the default size of the system menu icon. This // determines the height/width of the caption. // // The value that results from the following formula works out // nicely for the veritcal caption on VGA, 8514 (Large Fonts), // 8514 (Small Fonts), XGA (Small Fonts), XGA (Large Fonts), // and TIGA (Small Fonts). It may not be good on other displays. // // The problem is that TT fonts turn into bitmap fonts when they // are sized below a certain threshold. The idea is to make the // size of the caption just big enough to get the smallest TT // (scalable) font to fit. // #define CAPTIONXY (GetSystemMetrics( SM_CYCAPTION ) / 2 + 1) #define TestWinStyle( hWnd, dwStyleBit ) \ (((DWORD)GetWindowLong( hWnd, GWL_STYLE ) & (DWORD)dwStyleBit) ? TRUE : FALSE ) #define HASCAPTION( hwnd ) (TestWinStyle( hwnd, IBS_VERTCAPTION ) ||\ TestWinStyle( hwnd, IBS_HORZCAPTION )) #define SETCAPTIONSIZE(h,i) (UINT)SetProp(h,TEXT("ibSize"),(HANDLE)i) #define GETCAPTIONSIZE(h) (UINT)GetProp(h,TEXT("ibSize")) #define FREECAPTIONSIZE(h) RemoveProp(h,TEXT("ibSize")) #define SETMENUWASUPFLAG(h,i) (UINT)SetProp(h,TEXT("ibFlag"),(HANDLE)i) #define GETMENUWASUPFLAG(h) (UINT)GetProp(h,TEXT("ibFlag")) #define FREEMENUWASUPFLAG(h) RemoveProp(h,TEXT("ibFlag")) ///////////////////////////////////////////////////////////////////// // The following macro (DRAWFASTRECT) draws a filled rectangle // with no border and a solid color. It uses the current back- // ground color as the fill color. ////////////////////////////////////////////////////////////////////// #define USE_EXTTEXTOUT #ifdef USE_EXTTEXTOUT #define DRAWFASTRECT(hdc,lprc) ExtTextOut(hdc,0,0,ETO_OPAQUE,lprc,NULL,0,NULL) #else #define DRAWFASTRECT(hdc,lprc) {\ HBRUSH hbr = CreateSolidBrush( GetBkColor( hdc ) ) ;\ hbr = SelectObject(hdc, hbr) ;\ PatBlt(hdc,(lprc)->left,(lprc)->top,(lprc)->right-(lprc)->left,(lprc)->bottom-(lprc)->top,PATCOPY) ;\ hbr = SelectObject(hdc, hbr) ;\ DeleteObject( hbr ) ;\ } #endif // The DrawArrow function takes the following to indicate what // kind of arrow to draw. // #define ARROW_UP 0 #define ARROW_DOWN 1 #define ARROW_RESTORE 2 BOOL NEAR PASCAL DepressMinMaxButton( HWND hWnd, UINT uiHT, LPRECT ) ; BOOL NEAR PASCAL DoMenu( HWND hWnd ) ; void NEAR PASCAL SetupSystemMenu( HWND hWnd, HMENU hMenu ) ; BOOL NEAR PASCAL GetCaptionRect( HWND hWnd, LPRECT lprc ) ; BOOL NEAR PASCAL GetIconRect( HWND hWnd, LPRECT lprc ) ; BOOL NEAR PASCAL GetButtonRect( HWND hWnd, UINT nPos, LPRECT lprc ) ; BOOL NEAR PASCAL GetMinButtonRect( HWND hWnd, LPRECT lprc ) ; BOOL NEAR PASCAL GetMaxButtonRect( HWND hWnd, LPRECT lprc ) ; BOOL NEAR PASCAL DrawCaption( HDC hDC, HWND hWnd, LPRECT lprc, BOOL fVert, BOOL fSysMenu, BOOL fMin, BOOL fMax, BOOL fActive ) ; VOID NEAR PASCAL DrawSysMenu( HDC hDC, HWND hWnd, BOOL fInvert ) ; VOID NEAR PASCAL DrawButton( HDC hDC, HWND hWnd, BOOL fMin, BOOL fDepressed ) ; VOID NEAR PASCAL DrawArrow( HDC hdc, LPRECT lprc, UINT uiStyle ) ; // Global vars // static BOOL fWin31 ; /////////////////////////////////////////////////////////////////////// // External/Public functions /////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////// // UINT WINAPI ibGetCaptionSize( HWND hWnd ) // // Description: // // Gets the size of the caption (height if horz, width if // vertical). // // Comments: // /////////////////////////////////////////////////////////////// UINT WINAPI ibGetCaptionSize( HWND hWnd ) { return GETCAPTIONSIZE( hWnd ) + 1 ; } // ibSetCaptionSize() ///////////////////////////////////////////////////////////////// // UINT WINAPI ibSetCaptionSize( HWND hWnd, UINT nSize ) // // Description: // // Changes the size of the caption (height if horz, width if // vertical). // // Comments: // ////////////////////////////////////////////////////////////////// UINT WINAPI ibSetCaptionSize( HWND hWnd, UINT nSize ) { UINT ui ; if (nSize <= 0) return 0 ; nSize-- ; ui = SETCAPTIONSIZE( hWnd, nSize ) ; // Once we change the window style, we need a WM_NCCALCRECT // to be generated. // // SWP_FRAMECHANGED is not documented in the 3.1 SDK docs, // but *is* in WINDOWS.H. // SetWindowPos( hWnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER) ; return ui ; } // ibSetCaptionSize() ///////////////////////////////////////////////////////////////// // LRESULT WINAPI ibDefWindowProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) // // Description: // // This function should be called instead of DefWindowProc() for // windows that want to have itsybitsy captions. // // Comments: // ////////////////////////////////////////////////////////////////// LRESULT WINAPI ibDefWindowProc( HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam ) { LRESULT lRet ; UINT nCapSize ; switch( uiMsg ) { case WM_SYSCHAR: // If ALT-SPACE // was hit then pop up the menu // if (HASCAPTION( hWnd ) && (wParam == VK_SPACE)) { DoMenu( hWnd ) ; break ; } // FALL THROUGH!!!! // case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { DWORD dw = GetWindowLong( hWnd, GWL_STYLE ) ; // Fool DefWindowProc into thinking we do not have // a system menu. Otherwise it will try to // pop up its own. // SetWindowLong( hWnd, GWL_STYLE, dw &~WS_SYSMENU ) ; lRet = DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; SetWindowLong( hWnd, GWL_STYLE, dw ) ; return lRet ; } break ; case WM_COMMAND: // The menu that is poped up for the system menu with // TrackPopupMenu() sends it's notifications as WM_COMMAND // messages. We need to translate these into // WM_SYSCOMMAND messages. All standard WM_SYSCOMMAND // ids are greater than 0xF000. // // This could be a possible cause of confusion if the // itsybitsy window had children that used SC_MOVE or SC_CLOSE // as their IDs. Take note and be careful. // // Also, because ibDefWindowProc looks at WM_COMMAND messages, // you will need to be careful to call ibDefWindowProc() for // any wm_command messages that you would normally ignore. // Otherwise the system menu won't work. // if (wParam >= 0xF000) // Call PostMessage() here instead of SendMessage! // Here's why: // Our menu was created by TrackPopupMenu(). TPM() does // not return until after the menu has been destroyed // (and thus the command associated with the menu selection // sent). Therefore when we get here, we are still // *inside* TPM(). If we Send WM_SYSCOMMAND, SC_CLOSE // to the window, the window will be destroyed before // TPM() returns to our code within DoMenu() below. Wel... // We do stuff with the window handle after DoMenu() // returns (namely GetProp()). Since the window has // been destroyed,this is bad. PostMessage( hWnd, WM_SYSCOMMAND, wParam, lParam ) ; return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; case WM_GETMINMAXINFO: { nCapSize = GETCAPTIONSIZE( hWnd ) ; if (HASCAPTION( hWnd ) && TestWinStyle( hWnd, WS_THICKFRAME )) { LPPOINT lppt = (LPPOINT)lParam ; RECT rcMenu ; RECT rcMin ; RECT rcMax ; int nX ; int cx, cy ; // window frame/border width if (TestWinStyle( hWnd, WS_THICKFRAME )) { cx = GetSystemMetrics( SM_CXFRAME ) ; cy = GetSystemMetrics( SM_CYFRAME ) ; } else if (TestWinStyle(hWnd, WS_BORDER )) { cx = GetSystemMetrics( SM_CXBORDER ) ; cy = GetSystemMetrics( SM_CYBORDER ) ; } GetIconRect( hWnd, &rcMenu ) ; GetMinButtonRect( hWnd, &rcMin ) ; GetMaxButtonRect( hWnd, &rcMax ) ; nX = (rcMenu.right-rcMenu.left) + (rcMin.right-rcMin.left) + (rcMin.right-rcMin.left) ; if (TestWinStyle( hWnd, IBS_VERTCAPTION ) ) { lppt[3].x = nCapSize + cx * 2 - 1 ; lppt[3].y = nX + (2* nCapSize) ; } else { lppt[3].x = nX + (2* nCapSize) ; lppt[3].y = nCapSize + cy * 2 - 1 ; } } return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; } break ; ///////////////////////////////////////////////////////////////////// // Non-client area messages. These are used to allow the // minature caption bar to be handled correctly. // case WM_NCCREATE: { DWORD dwStyle ; // We have two things that we need to store somewhere: // 1) The caption height (width). // and 2) A flag indicating whether the sysmenu was // just up or not. // // CAPTIONXY is a macro that calls GetSystemMetrics. // SETCAPTIONSIZE( hWnd, CAPTIONXY ) ; // Set our flag that tells us whether the system menu was // 'just up'. // SETMENUWASUPFLAG( hWnd, FALSE ) ; // Are we in 3.1? If so we have some neat features // we can use like rotated TrueType fonts. // fWin31 = (BOOL)(LOWORD( GetVersion() ) >= 0x030A) ; // If IBS_????CAPTION was specified and the WS_DLGFRAME (or // WS_DLGFRAME 'cause it == WS_CAPTION | WS_BORDER) // was specified the creator made a mistake. Things get really // ugly if DefWindowProc sees WS_DLGFRAME, so we strip // the WS_DLGFRAME part off! // dwStyle = GetWindowLong( hWnd, GWL_STYLE ) ; if ((dwStyle & IBS_VERTCAPTION || dwStyle & IBS_HORZCAPTION) && dwStyle & WS_DLGFRAME) { dwStyle &= (DWORD)~WS_DLGFRAME ; SetWindowLong( hWnd, GWL_STYLE, dwStyle ) ; } } return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; case WM_NCDESTROY: // We store the caption size in a window prop. so we // must remove props. // FREECAPTIONSIZE( hWnd ) ; FREEMENUWASUPFLAG( hWnd ) ; return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; case WM_NCCALCSIZE: // This is sent when the window manager wants to find out // how big our client area is to be. If we have a mini-caption // then we trap this message and calculate the cleint area rect, // which is the client area rect calculated by DefWindowProc() // minus the width/height of the mini-caption bar // lRet = DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; if (!IsIconic( hWnd ) && HASCAPTION( hWnd )) { nCapSize = GETCAPTIONSIZE( hWnd ) ; if (TestWinStyle( hWnd, IBS_VERTCAPTION ) ) ((LPRECT)lParam)->left += nCapSize ; else ((LPRECT)lParam)->top += nCapSize ; } return lRet ; case WM_NCHITTEST: // This message is sent whenever the mouse moves over us. // We will depend on DefWindowProc for everything unless // there is a mini-caption, in which case we will // return HTCAPTION or HTSYSMENU. When the user clicks // or double clicks, NC_LBUTTON/ message are sent with // wParam equal to what we return here. // This means that this is an ideal place to figure out // where we are! // // let defwindowproc handle the standard borders etc... // lRet = DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; if (!IsIconic( hWnd ) && HASCAPTION( hWnd ) && lRet == HTNOWHERE) { RECT rc ; RECT rcMenu ; RECT rcMinButton ; RECT rcMaxButton ; POINT pt ; nCapSize = GETCAPTIONSIZE( hWnd ) ; // if DefWindowProc returned HTCAPTION then we have to // refine the area and return HTSYSMENU if appropriate // pt.x = LOWORD( lParam ) ; pt.y = HIWORD( lParam ) ; GetCaptionRect( hWnd, &rc ) ; // window coords if (PtInRect( &rc, pt )) { lRet = HTCAPTION ; // rely on the fact that Get???Rect() return an invalid // (empty) rectangle if the menu/buttons don't exist // GetIconRect( hWnd, &rcMenu ) ; GetMinButtonRect( hWnd, &rcMinButton ) ; GetMaxButtonRect( hWnd, &rcMaxButton ) ; if (PtInRect( &rcMenu, pt )) lRet = HTSYSMENU ; if (PtInRect( &rcMinButton, pt )) lRet = HTMINBUTTON ; else if (PtInRect( &rcMaxButton, pt )) lRet = HTMAXBUTTON ; } } if (lRet != HTSYSMENU) SETMENUWASUPFLAG( hWnd, FALSE ) ; return lRet ; case WM_NCLBUTTONDBLCLK: // Windows recieve WM_NC?BUTTONDBLCLK messages whether they // have CS_DBLCLKS or not. We watch for one of these // to see if the user double clicked on the system menu (to // close the window) or on the caption (to maximize the window). // if (!IsIconic( hWnd ) && HASCAPTION( hWnd ) && wParam == HTSYSMENU) { SendMessage( hWnd, WM_CLOSE, 0, 0L ) ; break ; } return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; case WM_NCLBUTTONDOWN: { RECT rc ; // If we're iconic or we don't have a caption then // DefWindowProc will do the job just fine. // if (IsIconic( hWnd ) || !HASCAPTION( hWnd )) return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; // Here's were we handle the system menu, the min and max buttons. // If you wanted to change the behavior of the min/max buttons // do something like swap tool palettes or something, you // would change the SendMessage() calls below. // switch (wParam) { case HTSYSMENU: if (GETMENUWASUPFLAG( hWnd ) == FALSE && DoMenu( hWnd )) SETMENUWASUPFLAG( hWnd, TRUE ) ; else SETMENUWASUPFLAG( hWnd, FALSE ) ; break ; case HTMINBUTTON: GetMinButtonRect( hWnd, &rc ) ; // Note that DepressMinMaxButton() goes into // a PeekMessage() loop waiting for the mouse // to come back up. // if (DepressMinMaxButton( hWnd, wParam, &rc )) SendMessage( hWnd, WM_SYSCOMMAND, SC_MINIMIZE, lParam ) ; break ; case HTMAXBUTTON: GetMaxButtonRect( hWnd, &rc ) ; // Note that DepressMinMaxButton() goes into // a PeekMessage() loop waiting for the mouse // to come back up. // if (DepressMinMaxButton( hWnd, wParam, &rc )) { if (IsZoomed(hWnd)) SendMessage( hWnd, WM_SYSCOMMAND, SC_RESTORE, lParam ) ; else SendMessage( hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, lParam ) ; } break ; default: // Well, it appears as though the user clicked somewhere other // than the buttons. We let DefWindowProc do it's magic. // This is where things like dragging and sizing the // window happen. // return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; } } break ; case WM_NCPAINT: case WM_NCACTIVATE: if (IsIconic( hWnd )) return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; // Paint the non-client area here. We will call DefWindowProc // after we are done so it can paint the borders and so // forth... // if (HASCAPTION( hWnd )) { RECT rcCap ; RECT rc ; HDC hDC = GetWindowDC( hWnd ) ; BOOL fActive ; GetCaptionRect( hWnd, &rcCap ) ; // Convert to window coords GetWindowRect( hWnd, &rc ) ; OffsetRect( &rcCap, -rc.left, -rc.top ) ; if (uiMsg == WM_NCPAINT) fActive = (hWnd == GetActiveWindow()) ; else fActive = wParam ; DrawCaption( hDC, hWnd, &rcCap, TestWinStyle(hWnd, IBS_VERTCAPTION), TestWinStyle(hWnd, WS_SYSMENU), TestWinStyle(hWnd, WS_MINIMIZEBOX), TestWinStyle(hWnd, WS_MAXIMIZEBOX), fActive ) ; ReleaseDC( hWnd, hDC ) ; } return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; default: return DefWindowProc( hWnd, uiMsg, wParam, lParam ) ; } return 0L ; } // ibDefWindowProc() // ibAdjustWindowRect( HWND hWnd, LPRECT lprc ) // // Does the same thing as the USER function AdjustWindowRect(), // but knows about itsybitsy windows. AdjustWindowRect() is // bogus for stuff like this. // VOID WINAPI ibAdjustWindowRect( HWND hWnd, LPRECT lprc ) { short cx = 0, cy = 0 ; UINT nCapSize ; nCapSize = GETCAPTIONSIZE( hWnd ) ; // First check Windows's styles, then our own. // if (TestWinStyle( hWnd, WS_THICKFRAME )) { cx = GetSystemMetrics( SM_CXFRAME ) ; cy = GetSystemMetrics( SM_CYFRAME ) ; } else if (TestWinStyle(hWnd, DS_MODALFRAME )) { cx = GetSystemMetrics( SM_CXDLGFRAME ) + GetSystemMetrics( SM_CXBORDER ) ; cy = GetSystemMetrics( SM_CYDLGFRAME ) + GetSystemMetrics( SM_CYBORDER ) ; } else if (TestWinStyle(hWnd, WS_BORDER )) { cx = GetSystemMetrics( SM_CXBORDER ) ; cy = GetSystemMetrics( SM_CYBORDER ) ; } InflateRect( lprc, cx, cy ) ; if (TestWinStyle( hWnd, IBS_VERTCAPTION )) lprc->left -= nCapSize ; else if (TestWinStyle( hWnd, IBS_HORZCAPTION )) lprc->top -= nCapSize ; } // ibAdjustWindowRect() /////////////////////////////////////////////////////////////////////// // Internal functions /////////////////////////////////////////////////////////////////////// // DepressMinMaxButton() // // This function is called when the user has pressed either the min or // max button (i.e. WM_NCLBUTTONDOWN). We go into a Peekmessage() loop, // waiting for the mouse to come back up. This allows us to make the // button change up/down state like a real button does. // // lprc points to the rectangle that describes the button the // user has clicked on. // BOOL NEAR PASCAL DepressMinMaxButton( HWND hWnd, UINT uiHT, LPRECT lprc ) { BOOL fDepressed = TRUE ; MSG msg ; // Draw button in down state DrawButton( NULL, hWnd, uiHT == HTMINBUTTON, fDepressed ) ; SetCapture( hWnd ) ; while (TRUE) { if (PeekMessage((LPMSG)&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) { switch (msg.message) { case WM_LBUTTONUP: if (fDepressed) DrawButton( NULL, hWnd, uiHT == HTMINBUTTON, !fDepressed ) ; ReleaseCapture(); return PtInRect( lprc, msg.pt ) ; case WM_MOUSEMOVE: if (PtInRect( lprc, msg.pt )) { if (!fDepressed) DrawButton( NULL, hWnd, uiHT == HTMINBUTTON, fDepressed = TRUE ) ; } else { if (fDepressed) DrawButton( NULL, hWnd, uiHT == HTMINBUTTON, fDepressed = FALSE ) ; } break; } } } } // DepressMinMaxButton() // DrawCaption( HDC hDC, HWND hWnd, LPRECT lprc, // BOOL fVert, BOOL fSysMenu, BOOL fActive ) // // This function draws an itsy bitsy caption bar with or // without system menu to the dc specified by hDC. The // caption is drawn to fit within the lprc RECT and is // drawn//withOut/ borders. // BOOL NEAR PASCAL DrawCaption( HDC hDC, HWND hWnd, LPRECT lprc, BOOL fVert, BOOL fSysMenu, BOOL fMin, BOOL fMax, BOOL fActive ) { RECT rc ; RECT rcCap ; COLORREF rgbCaptionBG ; COLORREF rgbText ; COLORREF rgbWindowFrame ; HBRUSH hbrCaption ; UINT ui ; UINT nCapSize ; nCapSize = GETCAPTIONSIZE( hWnd ) ; // Get the colors. // rgbWindowFrame = GetSysColor( COLOR_WINDOWFRAME ) ; // if we have focus use the active caption color // otherwise use the inactive caption color // if (fActive) { rgbText = GetSysColor( COLOR_CAPTIONTEXT ) ; rgbCaptionBG = GetSysColor( COLOR_ACTIVECAPTION ) ; } else { if (fWin31) rgbText = GetSysColor( COLOR_INACTIVECAPTIONTEXT ) ; else rgbText = GetSysColor( COLOR_CAPTIONTEXT ) ; rgbCaptionBG = GetSysColor( COLOR_INACTIVECAPTION ) ; } SetBkMode( hDC, TRANSPARENT ) ; SelectObject( hDC, GetStockObject( NULL_BRUSH ) ) ; SelectObject( hDC, GetStockObject( NULL_PEN ) ) ; rcCap = *lprc ; if (fSysMenu) { if (fVert) rcCap.top += nCapSize ; else rcCap.left += nCapSize ; } if (fMax) { if (fVert) rcCap.bottom -= nCapSize ; else rcCap.right -= nCapSize ; } if (fMin) { if (fVert) rcCap.bottom -= nCapSize ; else rcCap.right -= nCapSize ; } if (fVert) { rc.left = lprc->right - 1 ; rc.right = lprc->right ; rc.top = lprc->top ; rc.bottom = lprc->bottom ; } else { rc.left = lprc->left ; rc.right = lprc->right ; rc.bottom = lprc->bottom ; rc.top = rc.bottom - 1 ; } SetBkColor( hDC, rgbWindowFrame ) ; DRAWFASTRECT( hDC, &rc ) ; hbrCaption = CreateSolidBrush( rgbCaptionBG ) ; hbrCaption = SelectObject( hDC, hbrCaption ) ; SelectObject( hDC, GetStockObject( NULL_PEN ) ) ; if (fVert) Rectangle( hDC, rcCap.left, rcCap.top, rcCap.right, rcCap.bottom + 1 ) ; else Rectangle( hDC, rcCap.left, rcCap.top, rcCap.right+1, rcCap.bottom ) ; hbrCaption = SelectObject( hDC, hbrCaption ) ; DeleteObject( hbrCaption ) ; // Draw caption text here. Only do it in 3.1 'cause 3.1 gives // us 'small fonts'. // ui = GetWindowTextLength( hWnd ) ; if (fWin31) { HFONT hFont ; LPTSTR lpsz ; LOGFONT lf ; TEXTMETRIC tm ; int cx ; int cy ; SIZE Size ; if (lpsz = LocalAlloc( LPTR, (ui + 2) * sizeof(TCHAR) )) { UINT nBkMode ; GetWindowText( hWnd, lpsz, (ui + 1) * sizeof(TCHAR) ) ; nBkMode = SetBkMode( hDC, TRANSPARENT ) ; rgbText = SetTextColor( hDC, rgbText ) ; memset( &lf, '\0', sizeof(LOGFONT) ) ; lf.lfHeight = -(int)(nCapSize - 3) ; lf.lfCharSet = ANSI_CHARSET ; lf.lfQuality = DEFAULT_QUALITY ; lf.lfClipPrecision = CLIP_LH_ANGLES | CLIP_STROKE_PRECIS ; if (nCapSize >= 20) { lf.lfWeight = FW_BOLD ; } if (fVert) { // Can only rotate true type fonts (well, ok, we could // try and use "modern"). _tcscpy( lf.lfFaceName, TEXT("Arial") ) ; lf.lfPitchAndFamily = FF_SWISS | 0x04; lf.lfEscapement = 900 ; // Note: The Win 3.1 documentation for CreateFont() say's // that the lfOrientation member is ignored. It appears, // that on Windows 16 3.1 this is true, but when running // as a 16 bit WinApp on WindowsNT 3.1 the lfOrientation // must be set or the text does not rotate! // lf.lfOrientation = 900 ; hFont = CreateFontIndirect( &lf ) ; hFont = SelectObject( hDC, hFont ) ; GetTextExtentPoint( hDC, lpsz, ui, &Size ) ; cx = rcCap.bottom - ((rcCap.bottom - rcCap.top - Size.cx) / 2) ; cy = rcCap.left - 1 + ((rcCap.right - rcCap.left - Size.cy) / 2) ; // Make sure we got a rotatable font back. // GetTextMetrics( hDC, &tm ) ; if (tm.tmPitchAndFamily & TMPF_VECTOR || tm.tmPitchAndFamily & TMPF_TRUETYPE) { ExtTextOut( hDC, cy, min( cx, rcCap.bottom), ETO_CLIPPED, &rcCap, lpsz, ui, NULL ) ; } hFont = SelectObject( hDC, hFont ) ; DeleteObject( hFont ) ; } else { // Use small fonts always for the horizontal. Cause it looks // more like "System" than Arial. // lf.lfPitchAndFamily = FF_SWISS ; hFont = CreateFontIndirect( &lf ) ; hFont = SelectObject( hDC, hFont ) ; GetTextExtentPoint( hDC, lpsz, ui, &Size ) ; cx = rcCap.left + ((rcCap.right - rcCap.left - Size.cx) / 2) ; cy = rcCap.top + ((rcCap.bottom - rcCap.top - Size.cy) / 2) ; // Figger out how big the string is // ExtTextOut( hDC, max( cx, rcCap.left ), cy, ETO_CLIPPED, &rcCap, lpsz, ui, NULL ) ; hFont = SelectObject( hDC, hFont ) ; DeleteObject( hFont ) ; } // Unsetup the DC // rgbText = SetTextColor( hDC, rgbText ) ; SetBkMode( hDC, nBkMode ) ; LocalFree( lpsz ) ; } } if (fSysMenu) DrawSysMenu( hDC, hWnd, FALSE ) ; if (fMin) DrawButton( hDC, hWnd, TRUE, FALSE ) ; if (fMax) DrawButton( hDC, hWnd, FALSE, FALSE ) ; return TRUE ; } // DrawCaption() // DrawSysMenu( HDC hDC, hWnd, BOOL fInvert ) // // Draws the little system menu icon. // VOID NEAR PASCAL DrawSysMenu( HDC hDC, HWND hWnd, BOOL fInvert ) { RECT rcIcon ; RECT rcTemp ; RECT rc ; COLORREF rgbIconFace ; COLORREF rgbWindowFrame ; BOOL fDC ; UINT nCapSize ; nCapSize = GETCAPTIONSIZE( hWnd ) ; if (!hDC) { fDC = TRUE ; hDC = GetWindowDC( hWnd ) ; } else fDC = FALSE ; if (hDC) { rgbIconFace = GetNearestColor( hDC, RGBLTGRAY ) ; rgbWindowFrame = GetSysColor( COLOR_WINDOWFRAME ) ; GetIconRect( hWnd, &rcIcon ) ; GetWindowRect( hWnd, &rc ) ; OffsetRect( &rcIcon, -rc.left, -rc.top ) ; rcTemp = rcIcon ; if (TestWinStyle( hWnd, IBS_VERTCAPTION )) { rc = rcIcon ; // separator line rc.top = ++rc.bottom - 1 ; } else { rc = rcIcon ; // separator line rc.left = ++rc.right - 1 ; } // Fill SetBkColor( hDC, rgbIconFace ) ; DRAWFASTRECT( hDC, &rcTemp ) ; // Draw separator line SetBkColor( hDC, rgbWindowFrame ) ; DRAWFASTRECT( hDC, &rc ) ; if (nCapSize > 4) { // Draw the little horizontal doo-hickey // rcTemp.top = rcIcon.top + ((nCapSize-1) / 2) ; rcTemp.bottom = rcTemp.top + 3 ; rcTemp.left = rcTemp.left + 3 ; rcTemp.right = rcTemp.right - 1 ; SetBkColor( hDC, RGBGRAY ) ; DRAWFASTRECT( hDC, &rcTemp ) ; rc = rcTemp ; OffsetRect( &rc, -1, -1 ) ; SetBkColor( hDC, RGBBLACK ) ; DRAWFASTRECT( hDC, &rc ) ; InflateRect( &rc, -1, -1 ) ; SetBkColor( hDC, RGBWHITE ) ; DRAWFASTRECT( hDC, &rc ) ; } if (fInvert) InvertRect( hDC, &rcIcon ) ; if (fDC) ReleaseDC( hWnd, hDC ) ; } } // DrawSysMenu() // DoMenu( HWND hWnd ) // // Pops up the system menu. // BOOL NEAR PASCAL DoMenu( HWND hWnd ) { HDC hDC ; RECT rcIcon ; RECT rc ; POINT pt ; HMENU hMenu ; DWORD dw ; if (!TestWinStyle(hWnd, WS_SYSMENU)) return FALSE ; if (hDC = GetWindowDC( hWnd )) { // Invert the icon // DrawSysMenu( hDC, hWnd, TRUE ) ; // Pop up the menu // if (TestWinStyle( hWnd, IBS_VERTCAPTION )) { pt.x = -1 ; pt.y = 0 ; } else { pt.x = 0 ; pt.y = -1 ; } GetIconRect( hWnd, &rcIcon ) ; GetWindowRect( hWnd, &rc ) ; OffsetRect( &rcIcon, -rc.left, -rc.top ) ; ClientToScreen( hWnd, &pt ) ; ClientToScreen( hWnd, (LPPOINT)&rc.right ) ; dw = GetWindowLong( hWnd, GWL_STYLE ) ; SetWindowLong( hWnd, GWL_STYLE, dw | WS_SYSMENU ) ; hMenu = GetSystemMenu( hWnd, FALSE ) ; SetupSystemMenu( hWnd, hMenu ) ; SetWindowLong( hWnd, GWL_STYLE, dw ) ; TrackPopupMenu( hMenu, 0, //TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, &rc ) ; DrawSysMenu( hDC, hWnd, FALSE ) ; ReleaseDC( hWnd, hDC ) ; } return TRUE ; } // DoMenu() // SetupSystemMenu( HWND hWnd, HMENU hMenu ) // // Enables/Disables the appropriate menu items on the // menu passed for the window passed. // void NEAR PASCAL SetupSystemMenu( HWND hWnd, HMENU hMenu ) { UINT wMove ; UINT wSize ; UINT wMinBox ; UINT wMaxBox ; UINT wRestore ; // Assume all should be grayed. // wSize = wMove = wMinBox = wMaxBox = wRestore = MF_GRAYED ; if (TestWinStyle( hWnd, WS_MAXIMIZEBOX ) || IsIconic( hWnd )) wMaxBox = MF_ENABLED ; if (TestWinStyle( hWnd, WS_MINIMIZEBOX )) wMinBox = MF_ENABLED ; if (IsZoomed( hWnd )) wRestore = MF_ENABLED ; if (TestWinStyle( hWnd, WS_THICKFRAME ) && !(IsIconic( hWnd ) || IsZoomed( hWnd ))) wSize = MF_ENABLED ; if (!IsZoomed( hWnd ) && !IsIconic( hWnd ) && TestWinStyle( hWnd, WS_CAPTION ) ) wMove = MF_ENABLED ; EnableMenuItem( hMenu, SC_MOVE, wMove ) ; EnableMenuItem( hMenu, SC_SIZE, wSize ) ; EnableMenuItem( hMenu, SC_MINIMIZE, wMinBox ) ; EnableMenuItem( hMenu, SC_MAXIMIZE, wMaxBox ) ; EnableMenuItem( hMenu, SC_RESTORE, wRestore ) ; } // SetupSystemMenu() // GetCaptionRect( HWND hWnd, LPRECT lprc ) // // calcluales the rectangle of the mini-caption in screen coords. // BOOL NEAR PASCAL GetCaptionRect( HWND hWnd, LPRECT lprc ) { UINT nCapSize ; nCapSize = GETCAPTIONSIZE( hWnd ) ; if (!HASCAPTION( hWnd )) { SetRectEmpty( lprc ) ; return FALSE ; } GetWindowRect( hWnd, lprc ) ; // the window might have other non-client components like // borders // if (TestWinStyle( hWnd, WS_THICKFRAME )) { lprc->left += GetSystemMetrics( SM_CXFRAME ) ; lprc->top += GetSystemMetrics( SM_CYFRAME ) ; lprc->right -= GetSystemMetrics( SM_CXFRAME ) ; lprc->bottom -= GetSystemMetrics( SM_CYFRAME ) ; } else if (TestWinStyle( hWnd, DS_MODALFRAME )) // if it's a dialog box { lprc->left += GetSystemMetrics( SM_CXDLGFRAME ) + GetSystemMetrics( SM_CXBORDER ) ; lprc->top += GetSystemMetrics( SM_CYDLGFRAME ) + GetSystemMetrics( SM_CYBORDER ) ; lprc->right -= GetSystemMetrics( SM_CXDLGFRAME ) + GetSystemMetrics( SM_CXBORDER ) ; lprc->bottom -= GetSystemMetrics( SM_CYDLGFRAME ) + GetSystemMetrics( SM_CYBORDER ) ; } else if (TestWinStyle( hWnd, WS_BORDER )) { lprc->left += GetSystemMetrics( SM_CXBORDER ) ; lprc->top += GetSystemMetrics( SM_CYBORDER ) ; lprc->right -= GetSystemMetrics( SM_CXBORDER ) ; lprc->bottom -= GetSystemMetrics( SM_CYBORDER ) ; } if (TestWinStyle( hWnd, IBS_VERTCAPTION )) lprc->right = lprc->left + nCapSize ; else lprc->bottom = lprc->top + nCapSize ; return TRUE ; } // GetCaptionRect() // GetIconRect( HWND hWnd, LPRECT lprc ) // // Calculates the rect of the icon in screen coordinates. // BOOL NEAR PASCAL GetIconRect( HWND hWnd, LPRECT lprc ) { UINT nCapSize ; BOOL fMenu, fVert ; fMenu= TestWinStyle( hWnd, WS_SYSMENU ) ; fVert = TestWinStyle( hWnd, IBS_VERTCAPTION ) ; if (!GetCaptionRect( hWnd, lprc )) // window coords return FALSE ; if (!fMenu) { SetRectEmpty( lprc ) ; return FALSE ; } nCapSize = GETCAPTIONSIZE( hWnd ) ; if (fVert) lprc->bottom = lprc->top + nCapSize ; else lprc->right = lprc->left + nCapSize ; lprc->bottom-- ; lprc->right-- ; return TRUE ; } // GetIconRect() // GetMinButtonRect() // // Calculates the rect of the minimize button in screen // coordinates. // // For horizontal captions, we have the following situation ('Y' is minimize // and '^' is maximize or restore): // // +---------------------------------+ // | - | | Y | ^ | // +---------------------------------+ // | |.......| <-- This is the width (nSize) // // For vertical captions, we have the following: // // | | // | | // | | // | | // | | // | | // |--|-- // | Y| . // |--| . <-- This is the height of the rectangle (nSize) // | ^| . // +--+-- // // In order to figure out where the minimize button goes, we first need // to know if there is a maximize button. If so, use GetMaxButtonRect() // to place... // BOOL NEAR PASCAL GetMinButtonRect( HWND hWnd, LPRECT lprc ) { if (!TestWinStyle( hWnd, WS_MINIMIZEBOX )) { SetRectEmpty( lprc ) ; return FALSE ; } // The minimize button can be in either position 1 or 2. If there // is a maximize button, it's in position 2. // if (TestWinStyle( hWnd, WS_MAXIMIZEBOX )) return GetButtonRect( hWnd, 2, lprc ) ; else return GetButtonRect( hWnd, 1, lprc ) ; } // GetMaxButtonRect() // // Calculates the rect of the maximize button in screen // coordinates. // // The maximize button, if present, is always to the far right // or bottom. // BOOL NEAR PASCAL GetMaxButtonRect( HWND hWnd, LPRECT lprc ) { //The maximize button can only be in position 1. // if (TestWinStyle( hWnd, WS_MAXIMIZEBOX )) return GetButtonRect( hWnd, 1, lprc ) ; else { SetRectEmpty( lprc ) ; return FALSE ; } } // Get the rect where a button would go. // // This function does not care if it's a min or max, just whether // it is the first from the right/bottom or second from the right/bottom // and so on.. // BOOL NEAR PASCAL GetButtonRect( HWND hWnd, UINT nPos, LPRECT lprc ) { UINT nSize = 0 ; if (!GetCaptionRect( hWnd, lprc )) //window coords return FALSE ; nSize = GETCAPTIONSIZE( hWnd ) ; if (TestWinStyle( hWnd, IBS_VERTCAPTION )) { lprc->bottom -= nSize * (nPos-1) ; lprc->top = lprc->bottom - nSize + 1 ; } else { lprc->right -= nSize * (nPos-1) ; lprc->left = lprc->right - nSize + 1 ; } return TRUE ; } // GetButtonRect() // DrawButton( HDC hDC, HWND hWnd, BOOL fMin, BOOL fDepressed ) // // Draws either the min, max, or restore buttons. If fMin is FALSE then it // will draw either the Max or Restore button. If fDepressed is TRUE it will // draw the button in a down state. // VOID NEAR PASCAL DrawButton( HDC hDC, HWND hWnd, BOOL fMin, BOOL fDepressed) { RECT rcButton ; RECT rc ; COLORREF rgbWindowFrame ; BOOL fDC ; UINT nCapSize ; UINT nOffset ; int n ; nCapSize = GETCAPTIONSIZE( hWnd ) ; // If you look at the standard Windows' min/max buttons, you will notice // that they have two pixels of 'shadow' to the bottom and right. Since // our buttons can be really, really small, we only want one pixel of // shadow when they are small. I arbitrarily decided that if the // caption size is greater than or equal to 20 we will use two // pixels. That's what this THREASHOLD stuff does. // #define THRESHOLD 20 nOffset = (nCapSize >= THRESHOLD) ? 2 : 1 ; if (!hDC) { fDC = TRUE ; hDC = GetWindowDC( hWnd ) ; } else fDC = FALSE ; if (hDC) { rgbWindowFrame = GetSysColor( COLOR_WINDOWFRAME ) ; if (fMin) GetMinButtonRect( hWnd, &rcButton ) ; else GetMaxButtonRect( hWnd, &rcButton ) ; GetWindowRect( hWnd, &rc ) ; OffsetRect( &rcButton, -rc.left, -rc.top ) ; rc = rcButton ; if (TestWinStyle( hWnd, IBS_VERTCAPTION )) { rc = rcButton ; //separator line rc.bottom = --rc.top + 1 ; rcButton.right-- ; } else { rc = rcButton ; //separator line rc.right = --rc.left + 1 ; rcButton.bottom-- ; } //Draw separator line SetBkColor( hDC, rgbWindowFrame ) ; DRAWFASTRECT( hDC, &rc ) ; //Fill SetBkColor( hDC, RGBLTGRAY ) ; DRAWFASTRECT( hDC, &rcButton ) ; if (!fDepressed) { //The normal min/max buttons have one pixel on the top and left //sides for the highlight, and two pixels on the bottom and //right side for the shadow. // //When our caption is 'small' we only use one pixel on all //sides. // SetBkColor( hDC, RGBWHITE ) ; //Draw left side rc = rcButton ; rc.right = rc.left + 1 ; DRAWFASTRECT( hDC, &rc ) ; //Draw Top rc = rcButton ; rc.bottom = rc.top + 1 ; DRAWFASTRECT( hDC, &rc ) ; SetBkColor( hDC, RGBGRAY ) ; //Draw right side rc = rcButton ; rc.left = rc.right - 1 ; DRAWFASTRECT( hDC, &rc ) ; if (nCapSize > THRESHOLD) { rc.left-- ; rc.top++ ; DRAWFASTRECT( hDC, &rc ) ; } //Draw bottom rc = rcButton ; rc.top = rc.bottom - 1 ; DRAWFASTRECT( hDC, &rc ) ; if (nCapSize > THRESHOLD) { rc.top-- ; rc.left++ ; DRAWFASTRECT( hDC, &rc ) ; } rcButton.left++ ; rcButton.top++ ; rcButton.right -= nOffset ; rcButton.bottom -= nOffset ; } else { //Draw depressed state SetBkColor( hDC, RGBGRAY ) ; //Draw left side rc = rcButton ; rc.right = rc.left + nOffset ; DRAWFASTRECT( hDC, &rc ) ; //Draw Top rc = rcButton ; rc.bottom = rc.top + nOffset ; DRAWFASTRECT( hDC, &rc ) ; rcButton.left += 2 * nOffset ; rcButton.top += 2 * nOffset ; } // Now draw the arrows. We do not want the // arrows to grow too large when we have a bigger than // normal caption, so we restrict their size. // // rcButton now represents where we can place our // arrows. // // The maximum size of our arrows (i.e. the width of rcButton) // has been empirically determined to be SM_CYCAPTION / 2 // n = ((GetSystemMetrics( SM_CYCAPTION )) / 2) - (rcButton.right - rcButton.left) ; if (n < 1) InflateRect( &rcButton, n/2-1, n/2-1 ) ; if (fMin) DrawArrow( hDC, &rcButton, ARROW_DOWN ) ; else if (IsZoomed( hWnd )) { DrawArrow( hDC, &rcButton, ARROW_RESTORE ) ; } else DrawArrow( hDC, &rcButton, ARROW_UP ) ; if (fDC) ReleaseDC( hWnd, hDC ) ; } } // DrawButton() // DrawArrow // // Draws either a up or down arrow. The arrow is bound by the rectangle // VOID NEAR PASCAL DrawArrow( HDC hdc, LPRECT lprc, UINT uiStyle ) { int row ; int xTip ; int yTip ; RECT rc ; int nMax = (lprc->bottom - lprc->top) >> 1 ; SetBkColor( hdc, RGBBLACK ) ; // We draw the arrow by drawing a series of horizontal lines // xTip = lprc->left + ((lprc->right - lprc->left+1) >> 1) ; switch (uiStyle) { case ARROW_UP: yTip = lprc->top + ((lprc->bottom - lprc->top-1) >> 2) ; for (row = 1 ; row <= nMax ; row++ ) { rc.left = xTip - row ; rc.right = xTip + row - 1 ; rc.top = yTip + row ; rc.bottom = rc.top + 1 ; DRAWFASTRECT( hdc, &rc ) ; } break ; case ARROW_DOWN: yTip = lprc->bottom - ((lprc->bottom - lprc->top-1) >> 2) ; for ( row = nMax ; row > 0 ; row-- ) { rc.left = xTip - row ; rc.right = xTip + row - 1 ; rc.top = yTip - row ; rc.bottom = rc.top + 1 ; DRAWFASTRECT( hdc, &rc ) ; } break ; case ARROW_RESTORE: default: yTip = lprc->top + ((lprc->bottom - lprc->top-1) >> 3) - 2; for (row = 1 ; row <= nMax ; row++ ) { rc.left = xTip - row ; rc.right = xTip + row - 1 ; rc.top = yTip + row ; rc.bottom = rc.top + 1 ; DRAWFASTRECT( hdc, &rc ) ; } yTip += (nMax+1) * 2 ; for ( row = nMax ; row > 0 ; row-- ) { rc.left = xTip - row ; rc.right = xTip + row - 1 ; rc.top = yTip - row ; rc.bottom = rc.top + 1 ; DRAWFASTRECT( hdc, &rc ) ; } break ; } } // DrawArrow() // This function is obsolete. Some of my apps still call it // though so that's why it's still here. // BOOL FAR PASCAL ibInit( HANDLE hInstance ) { return TRUE ; } ///////////////////////////////////////////////////////////////////////// // End of File: itsybits.c /////////////////////////////////////////////////////////////////////////