/* Copyright (c) 1995, Microsoft Corporation, all rights reserved ** ** ui.c ** UI helper routines ** Listed alphabetically ** ** 08/25/95 Steve Cobb */ #include // Win32 root #include // Win32 macro extensions #include // Win32 common controls #include // Trace and assert #include // Our public header /*---------------------------------------------------------------------------- ** Globals **---------------------------------------------------------------------------- */ /* See SetOffDesktop. */ static LPCWSTR g_SodContextId = NULL; /* Set when running in a mode where WinHelp does not work. This is a ** workaround to the problem where WinHelp does not work correctly before a ** user is logged on. See AddContextHelpButton. */ BOOL g_fNoWinHelp = FALSE; /*---------------------------------------------------------------------------- ** Local datatypes **---------------------------------------------------------------------------- */ /* SetOffDesktop context. */ #define SODINFO struct tagSODINFO SODINFO { RECT rectOrg; BOOL fWeMadeInvisible; }; /*---------------------------------------------------------------------------- ** Local prototypes **---------------------------------------------------------------------------- */ BOOL CALLBACK CancelOwnedWindowsEnumProc( IN HWND hwnd, IN LPARAM lparam ); BOOL CALLBACK CloseOwnedWindowsEnumProc( IN HWND hwnd, IN LPARAM lparam ); /*---------------------------------------------------------------------------- ** Utility routines **---------------------------------------------------------------------------- */ VOID AddContextHelpButton( IN HWND hwnd ) /* Turns on title bar context help button in 'hwnd'. ** ** Dlgedit.exe doesn't currently support adding this style at dialog ** resource edit time. When that's fixed set DS_CONTEXTHELP in the dialog ** definition and remove this routine. */ { LONG lStyle; if (g_fNoWinHelp) return; lStyle = GetWindowLong( hwnd, GWL_EXSTYLE ); if (lStyle) SetWindowLong( hwnd, GWL_EXSTYLE, lStyle | WS_EX_CONTEXTHELP ); } VOID Button_MakeDefault( IN HWND hwndDlg, IN HWND hwndPb ) /* Make 'hwndPb' the default button on dialog 'hwndDlg'. */ { DWORD dwResult; HWND hwndPbOldDefault; dwResult = (DWORD) SendMessage( hwndDlg, DM_GETDEFID, 0, 0 ); if (HIWORD( dwResult ) == DC_HASDEFID) { /* Un-default the current default button. */ hwndPbOldDefault = GetDlgItem( hwndDlg, LOWORD( dwResult ) ); Button_SetStyle( hwndPbOldDefault, BS_PUSHBUTTON, TRUE ); } /* Set caller's button to the default. */ SendMessage( hwndDlg, DM_SETDEFID, GetDlgCtrlID( hwndPb ), 0 ); Button_SetStyle( hwndPb, BS_DEFPUSHBUTTON, TRUE ); } HBITMAP Button_CreateBitmap( IN HWND hwndPb, IN BITMAPSTYLE bitmapstyle ) /* Creates a bitmap of 'bitmapstyle' suitable for display on 'hwndPb. The ** 'hwndPb' must have been created with BS_BITMAP style. ** ** 'HwndPb' may be a checkbox with BS_PUSHLIKE style, in which case the ** button locks down when pressed like a toolbar button. This case ** requires that a color bitmap be created resulting in two extra ** restrictions. First, caller must handle WM_SYSCOLORCHANGE and rebuild ** the bitmaps with the new colors and second, the button cannot be ** disabled. ** ** Returns the handle to the bitmap. Caller can display it on the button ** as follows: ** ** SendMessage( hwndPb, BM_SETIMAGE, 0, (LPARAM )hbitmap ); ** ** Caller is responsible for calling DeleteObject(hbitmap) when done using ** the bitmap, typically when the dialog is destroyed. ** ** (Adapted from a routine by Tony Romano) */ { RECT rect; HDC hdc; HDC hdcMem; HBITMAP hbitmap; HFONT hfont; HPEN hpen; SIZE sizeText; SIZE sizeBitmap; INT x; INT y; TCHAR* psz; TCHAR* pszText; TCHAR* pszText2; DWORD dxBitmap; DWORD dxBetween; BOOL fOnRight; BOOL fPushLike; hdc = NULL; hdcMem = NULL; hbitmap = NULL; hpen = NULL; pszText = NULL; pszText2 = NULL; switch (bitmapstyle) { case BMS_UpArrowOnLeft: case BMS_DownArrowOnLeft: case BMS_UpArrowOnRight: case BMS_DownArrowOnRight: dxBitmap = 5; dxBetween = 4; break; case BMS_UpTriangleOnLeft: case BMS_DownTriangleOnLeft: case BMS_UpTriangleOnRight: case BMS_DownTriangleOnRight: dxBitmap = 7; dxBetween = 6; break; default: return NULL; } fOnRight = (bitmapstyle & BMS_OnRight); fPushLike = (GetWindowLong( hwndPb, GWL_STYLE ) & BS_PUSHLIKE); /* Get a memory DC compatible with the button window. */ hdc = GetDC( hwndPb ); if (!hdc) return NULL; hdcMem = CreateCompatibleDC( hdc ); if (!hdcMem) goto BCB_Error; /* Create a compatible bitmap covering the entire button in the memory DC. ** ** For a push button, the bitmap is created compatible with the memory DC, ** NOT the display DC. This causes the bitmap to be monochrome, the ** default for memory DCs. When GDI maps monochrome bitmaps into color, ** white is replaced with the background color and black is replaced with ** the text color, which is exactly what we want. With this technique, we ** are relieved from explicit handling of changes in system colors. ** ** For a push-like checkbox the bitmap is created compatible with the ** button itself, so the bitmap is typically color. */ GetClientRect( hwndPb, &rect ); hbitmap = CreateCompatibleBitmap( (fPushLike) ? hdc : hdcMem, rect.right, rect.bottom ); if (!hbitmap) goto BCB_Error; ReleaseDC( hwndPb, hdc ); hdc = NULL; SelectObject( hdcMem, hbitmap ); /* Select the font the button says it's using. */ hfont = (HFONT )SendMessage( hwndPb, WM_GETFONT, 0, 0 ); if (hfont) SelectObject( hdcMem, hfont ); /* Set appropriate colors for regular and stuck-down states. Don't need ** to do anything for the monochrome case as the default black pen and ** white background are what we want. */ if (fPushLike) { INT nColor; if (bitmapstyle == BMS_UpArrowOnLeft || bitmapstyle == BMS_UpArrowOnRight || bitmapstyle == BMS_UpTriangleOnLeft || bitmapstyle == BMS_UpTriangleOnRight) { nColor = COLOR_BTNHILIGHT; } else { nColor = COLOR_BTNFACE; } SetBkColor( hdcMem, GetSysColor( nColor ) ); hpen = CreatePen( PS_SOLID, 0, GetSysColor( COLOR_BTNTEXT ) ); if (hpen) SelectObject( hdcMem, hpen ); } /* The created bitmap is random, so we erase it to the background color. ** No text is written here. */ ExtTextOut( hdcMem, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL ); /* Get the button label and make a copy with the '&' accelerator-escape ** removed, which would otherwise mess up our width calculations. */ pszText = GetText( hwndPb ); pszText2 = StrDup( pszText ); if (!pszText || !pszText2) goto BCB_Error; for (psz = pszText2; *psz; psz = CharNext( psz ) ) { if (*psz == TEXT('&')) { lstrcpy( psz, psz + 1 ); break; } } /* Calculate the width of the button label text. */ sizeText.cx = 0; sizeText.cy = 0; GetTextExtentPoint32( hdcMem, pszText2, lstrlen( pszText2 ), &sizeText ); /* Draw the text off-center horizontally enough so it is centered with the ** bitmap symbol added. */ --rect.bottom; sizeBitmap.cx = dxBitmap; sizeBitmap.cy = 0; rect.left += ((rect.right - (sizeText.cx + sizeBitmap.cx) - dxBetween) / 2); if (fOnRight) { DrawText( hdcMem, pszText, -1, &rect, DT_VCENTER + DT_SINGLELINE + DT_EXTERNALLEADING ); rect.left += sizeText.cx + dxBetween; } else { rect.left += dxBitmap + dxBetween; DrawText( hdcMem, pszText, -1, &rect, DT_VCENTER + DT_SINGLELINE + DT_EXTERNALLEADING ); rect.left -= dxBitmap + dxBetween; } /* Eliminate the top and bottom 3 pixels of button from consideration for ** the bitmap symbol. This leaves the button control room to do the ** border and 3D edges. */ InflateRect( &rect, 0, -3 ); /* Draw the bitmap symbol. The rectangle is now 'dxBitmap' wide and ** centered vertically with variable height depending on the button size. */ switch (bitmapstyle) { case BMS_UpArrowOnLeft: case BMS_UpArrowOnRight: { /* v-left ** ..... <-top ** ..... ** ..... ** ..*.. \ ** .***. | ** ***** | ** ..*.. | ** ..*.. | ** ..*.. > varies depending on font height ** ..*.. | ** ..*.. | ** ..*.. | ** ..*.. | ** ..*.. / ** ..... ** ..... ** ..... ** ..... <-bottom */ /* Draw the vertical line. */ x = rect.left + 2; y = rect.top + 3; MoveToEx( hdcMem, x, y, NULL ); LineTo( hdcMem, x, rect.bottom - 3 ); /* Draw the 2 crossbars. */ MoveToEx( hdcMem, x - 1, ++y, NULL ); LineTo( hdcMem, x + 2, y ); MoveToEx( hdcMem, x - 2, ++y, NULL ); LineTo( hdcMem, x + 3, y ); break; } case BMS_DownArrowOnLeft: case BMS_DownArrowOnRight: { /* v-left ** ..... <-top ** ..... ** ..... ** ..*.. \ ** ..*.. | ** ..*.. | ** ..*.. | ** ..*.. | ** ..*.. > varies depending on font height ** ..*.. | ** ..*.. | ** ***** | ** .***. | ** ..*.. / ** ..... ** ..... ** ..... ** ..... <-bottom */ /* Draw the vertical line. */ x = rect.left + 2; y = rect.top + 3; MoveToEx( hdcMem, x, y, NULL ); LineTo( hdcMem, x, rect.bottom - 3 ); /* Draw the 2 crossbars. */ y = rect.bottom - 6; MoveToEx( hdcMem, x - 2, y, NULL ); LineTo( hdcMem, x + 3, y ); MoveToEx( hdcMem, x - 1, ++y, NULL ); LineTo( hdcMem, x + 2, y ); break; } case BMS_UpTriangleOnLeft: case BMS_UpTriangleOnRight: { /* v-left ** ....... <-top ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... ** ...o... <- o indicates x,y origin ** ..***.. ** .*****. ** ******* ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... <-bottom */ x = rect.left + 3; y = ((rect.bottom - rect.top) / 2) + 2; MoveToEx( hdcMem, x, y, NULL ); LineTo( hdcMem, x + 1, y ); ++y; MoveToEx( hdcMem, x - 1, y, NULL ); LineTo( hdcMem, x + 2, y ); ++y; MoveToEx( hdcMem, x - 2, y, NULL ); LineTo( hdcMem, x + 3, y ); ++y; MoveToEx( hdcMem, x - 3, y, NULL ); LineTo( hdcMem, x + 4, y ); break; } case BMS_DownTriangleOnLeft: case BMS_DownTriangleOnRight: { /* v-left ** ....... <-top ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... ** ***o*** <- o indicates x,y origin ** .*****. ** ..***.. ** ...*... ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... ** ....... <-bottom */ x = rect.left + 3; y = ((rect.bottom - rect.top) / 2) + 2; MoveToEx( hdcMem, x - 3, y, NULL ); LineTo( hdcMem, x + 4, y ); ++y; MoveToEx( hdcMem, x - 2, y, NULL ); LineTo( hdcMem, x + 3, y ); ++y; MoveToEx( hdcMem, x - 1, y, NULL ); LineTo( hdcMem, x + 2, y ); ++y; MoveToEx( hdcMem, x, y, NULL ); LineTo( hdcMem, x + 1, y ); break; } } BCB_Error: Free0( pszText ); Free0( pszText2 ); if (hdc) ReleaseDC( hwndPb, hdc ); if (hdcMem) DeleteDC( hdcMem ); if (hpen) DeleteObject( hpen ); return hbitmap; } VOID CenterWindow( IN HWND hwnd, IN HWND hwndRef ) /* Center window 'hwnd' on window 'hwndRef' or if 'hwndRef' is NULL on ** screen. The window position is adjusted so that no parts are clipped ** by the edge of the screen, if necessary. If 'hwndRef' has been moved ** off-screen with SetOffDesktop, the original position is used. */ { RECT rectCur; LONG dxCur; LONG dyCur; RECT rectRef; LONG dxRef; LONG dyRef; GetWindowRect( hwnd, &rectCur ); dxCur = rectCur.right - rectCur.left; dyCur = rectCur.bottom - rectCur.top; if (hwndRef) { if (!SetOffDesktop( hwndRef, SOD_GetOrgRect, &rectRef )) GetWindowRect( hwndRef, &rectRef ); } else { rectRef.top = rectRef.left = 0; rectRef.right = GetSystemMetrics( SM_CXSCREEN ); rectRef.bottom = GetSystemMetrics( SM_CYSCREEN ); } dxRef = rectRef.right - rectRef.left; dyRef = rectRef.bottom - rectRef.top; rectCur.left = rectRef.left + ((dxRef - dxCur) / 2); rectCur.top = rectRef.top + ((dyRef - dyCur) / 2); SetWindowPos( hwnd, NULL, rectCur.left, rectCur.top, 0, 0, SWP_NOZORDER + SWP_NOSIZE ); UnclipWindow( hwnd ); } //Add this function for whislter bug 320863 gangz // //Center expand the window horizontally in its parent window // this expansion will remain the left margin between the child window // and the parent window // hwnd: Child Window // hwndRef: Reference window // bHoriz: TRUE, means expand horizontally, let the right margin equal the left margin // bVert: TRUE, elongate the height proportial to the width; // hwndVertBound: the window that our vertical expandasion cannot overlap with // VOID CenterExpandWindowRemainLeftMargin( IN HWND hwnd, IN HWND hwndRef, BOOL bHoriz, BOOL bVert, IN HWND hwndVertBottomBound) { RECT rectCur, rectRef, rectVbBound; LONG dxCur, dyCur, dxRef; POINT ptTemp; double ratio; BOOL bvbBound = FALSE; if (hwnd) { GetWindowRect( hwnd, &rectCur ); if (hwndRef) { GetWindowRect( hwndRef, &rectRef ); if(hwndVertBottomBound) { GetWindowRect( hwndVertBottomBound, &rectVbBound ); //We only consider normal cases, if hwnd and hwndVertBound are already //overlapped, that is the problem beyond this function. // if ( rectCur.top < rectVbBound.top ) { bvbBound = TRUE; } } dxRef = rectRef.right - rectRef.left +1; dxCur = rectCur.right - rectCur.left +1; dyCur = rectCur.bottom - rectCur.top +1; ratio = dyCur*1.0/dxCur; if(bHoriz) { rectCur.right = rectRef.right - (rectCur.left - rectRef.left); } if(bVert) { rectCur.bottom = rectCur.top + (LONG)( (rectCur.right - rectCur.left+1) * ratio ); } if(bvbBound) { //if overlapping occurs w/o expansion, we need to fix it //then we do the vertical centering, // this bounding is basically for JPN bugs 329700 329715 which // wont happen on English build // if(rectCur.bottom > rectVbBound.top ) { LONG dxResult, dyResult, dyVRef; dyResult = rectVbBound.top - rectCur.top; dxResult = (LONG)(dyResult/ratio); ptTemp.x = rectVbBound.left; ptTemp.y = rectVbBound.top-1; //For whistler bug 371914 gangz //Cannot use ScreenToClient() here //On RTL build, we must use MapWindowPoint() instead // MapWindowPoints(HWND_DESKTOP, hwndRef, &ptTemp, 1); dyVRef = ptTemp.y + 1; rectCur.left = rectRef.left + (dxRef - dxResult)/2; rectCur.right = rectRef.right - (dxRef-dxResult)/2; rectCur.bottom = rectVbBound.top - (dyVRef-dyResult)/2; rectCur.top = rectCur.bottom - dyResult; } } ptTemp.x = rectCur.left; ptTemp.y = rectCur.top; MapWindowPoints(HWND_DESKTOP, hwndRef, &ptTemp, 1); rectCur.left = ptTemp.x; rectCur.top = ptTemp.y; ptTemp.x = rectCur.right; ptTemp.y = rectCur.bottom; MapWindowPoints(HWND_DESKTOP, hwndRef, &ptTemp, 1); rectCur.right = ptTemp.x; rectCur.bottom = ptTemp.y; //For mirrored build // if ( rectCur.right < rectCur.left ) { int tmp; tmp = rectCur.right; rectCur.right = rectCur.left; rectCur.left = tmp; } SetWindowPos( hwnd, NULL, rectCur.left, rectCur.top, rectCur.right - rectCur.left + 1, rectCur.bottom - rectCur.top +1, SWP_NOZORDER); } } } VOID CancelOwnedWindows( IN HWND hwnd ) /* Sends WM_COMMAND(IDCANCEL) to all windows that are owned by 'hwnd' in ** the current thread. */ { EnumThreadWindows( GetCurrentThreadId(), CloseOwnedWindowsEnumProc, (LPARAM )hwnd ); } BOOL CALLBACK CancelOwnedWindowsEnumProc( IN HWND hwnd, IN LPARAM lparam ) /* Standard Win32 EnumThreadWindowsWndProc used by CancelOwnedWindows. */ { HWND hwndThis; for (hwndThis = GetParent( hwnd ); hwndThis; hwndThis = GetParent( hwndThis )) { if (hwndThis == (HWND )lparam) { FORWARD_WM_COMMAND( hwnd, IDCANCEL, NULL, 0, SendMessage ); break; } } return TRUE; } VOID CloseOwnedWindows( IN HWND hwnd ) /* Sends WM_CLOSE to all windows that are owned by 'hwnd' in the current ** thread. */ { EnumThreadWindows( GetCurrentThreadId(), CloseOwnedWindowsEnumProc, (LPARAM )hwnd ); } BOOL CALLBACK CloseOwnedWindowsEnumProc( IN HWND hwnd, IN LPARAM lparam ) /* Standard Win32 EnumThreadWindowsWndProc used by CloseOwnedWindows. */ { HWND hwndThis; for (hwndThis = GetParent( hwnd ); hwndThis; hwndThis = GetParent( hwndThis )) { if (hwndThis == (HWND )lparam) { SendMessage( hwnd, WM_CLOSE, 0, 0 ); break; } } return TRUE; } INT ComboBox_AddItem( IN HWND hwndLb, IN LPCTSTR pszText, IN VOID* pItem ) /* Adds data item 'pItem' with displayed text 'pszText' to listbox ** 'hwndLb'. The item is added sorted if the listbox has LBS_SORT style, ** or to the end of the list otherwise. If the listbox has LB_HASSTRINGS ** style, 'pItem' is a null terminated string, otherwise it is any user ** defined data. ** ** Returns the index of the item in the list or negative if error. */ { INT nIndex; nIndex = ComboBox_AddString( hwndLb, pszText ); if (nIndex >= 0) ComboBox_SetItemData( hwndLb, nIndex, pItem ); return nIndex; } INT ComboBox_AddItemFromId( IN HINSTANCE hinstance, IN HWND hwndLb, IN DWORD dwStringId, IN VOID* pItem ) /* Adds data item 'pItem' to listbox 'hwndLb'. 'dwStringId' is the string ** ID of the item's displayed text. 'Hinstance' is the app or module ** instance handle. ** ** Returns the index of the item in the list or negative if error. */ { INT i; LPCTSTR psz; psz = PszLoadString( hinstance, dwStringId ); if (psz) { i = ComboBox_AddItem( hwndLb, psz, pItem ); } else { i = LB_ERRSPACE; } return i; } INT ComboBox_AddItemSorted( IN HWND hwndLb, IN LPCTSTR pszText, IN VOID* pItem ) /* Adds data item 'pItem' with displayed text 'pszText' to listbox ** 'hwndLb' in order sorted by 'pszText'. It is assumed all items added ** to the list to this point are sorted. If the listbox has LB_HASSTRINGS ** style, 'pItem' is a null terminated string, otherwise it is any user ** defined data. ** ** Returns the index of the item in the list or negative if error. */ { INT nIndex; INT i; INT c; c = ComboBox_GetCount( hwndLb ); for (i = 0; i < c; ++i) { TCHAR* psz; psz = ComboBox_GetPsz( hwndLb, i ); if (psz) { if (lstrcmp( pszText, psz ) < 0) break; Free( psz ); } } if (i >= c) i = -1; nIndex = ComboBox_InsertString( hwndLb, i, pszText ); if (nIndex >= 0) ComboBox_SetItemData( hwndLb, nIndex, pItem ); return nIndex; } VOID ComboBox_AutoSizeDroppedWidth( IN HWND hwndLb ) /* Set the width of the drop-down list 'hwndLb' to the width of the ** longest item (or the width of the list box if that's wider). */ { HDC hdc; HFONT hfont; TCHAR* psz; SIZE size; DWORD cch; DWORD dxNew; DWORD i; hfont = (HFONT )SendMessage( hwndLb, WM_GETFONT, 0, 0 ); if (!hfont) return; hdc = GetDC( hwndLb ); if (!hdc) return; SelectObject( hdc, hfont ); dxNew = 0; for (i = 0; psz = ComboBox_GetPsz( hwndLb, i ); ++i) { cch = lstrlen( psz ); if (GetTextExtentPoint32( hdc, psz, cch, &size )) { if (dxNew < (DWORD )size.cx) dxNew = (DWORD )size.cx; } Free( psz ); } ReleaseDC( hwndLb, hdc ); /* Allow for the spacing on left and right added by the control. */ dxNew += 6; /* Figure out if the vertical scrollbar will be displayed and, if so, ** allow for it's width. */ { RECT rectD; RECT rectU; DWORD dyItem; DWORD cItemsInDrop; DWORD cItemsInList; GetWindowRect( hwndLb, &rectU ); SendMessage( hwndLb, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM )&rectD ); dyItem = (DWORD)SendMessage( hwndLb, CB_GETITEMHEIGHT, 0, 0 ); cItemsInDrop = (rectD.bottom - rectU.bottom) / dyItem; cItemsInList = ComboBox_GetCount( hwndLb ); if (cItemsInDrop < cItemsInList) dxNew += GetSystemMetrics( SM_CXVSCROLL ); } SendMessage( hwndLb, CB_SETDROPPEDWIDTH, dxNew, 0 ); } #if 0 VOID ComboBox_FillFromPszList( IN HWND hwndLb, IN DTLLIST* pdtllistPsz ) /* Loads 'hwndLb' with an item form each node in the list strings, ** 'pdtllistPsz'. */ { DTLNODE* pNode; if (!pdtllistPsz) return; for (pNode = DtlGetFirstNode( pdtllistPsz ); pNode; pNode = DtlGetNextNode( pNode )) { TCHAR* psz; psz = (TCHAR* )DtlGetData( pNode ); ASSERT(psz); ComboBox_AddString( hwndLb, psz ); } } #endif VOID* ComboBox_GetItemDataPtr( IN HWND hwndLb, IN INT nIndex ) /* Returns the address of the 'nIndex'th item context in 'hwndLb' or NULL ** if none. */ { LRESULT lResult; if (nIndex < 0) return NULL; lResult = ComboBox_GetItemData( hwndLb, nIndex ); if (lResult < 0) return NULL; return (VOID* )lResult; } TCHAR* ComboBox_GetPsz( IN HWND hwnd, IN INT nIndex ) /* Returns heap block containing the text contents of the 'nIndex'th item ** of combo box 'hwnd' or NULL. It is caller's responsibility to Free the ** returned string. */ { INT cch; TCHAR* psz; cch = ComboBox_GetLBTextLen( hwnd, nIndex ); if (cch < 0) return NULL; psz = Malloc( (cch + 1) * sizeof(TCHAR) ); if (psz) { *psz = TEXT('\0'); ComboBox_GetLBText( hwnd, nIndex, psz ); } return psz; } VOID ComboBox_SetCurSelNotify( IN HWND hwndLb, IN INT nIndex ) /* Set selection in listbox 'hwndLb' to 'nIndex' and notify parent as if ** user had clicked the item which Windows doesn't do for some reason. */ { ComboBox_SetCurSel( hwndLb, nIndex ); SendMessage( GetParent( hwndLb ), WM_COMMAND, (WPARAM )MAKELONG( (WORD )GetDlgCtrlID( hwndLb ), (WORD )CBN_SELCHANGE ), (LPARAM )hwndLb ); } TCHAR* Ellipsisize( IN HDC hdc, IN TCHAR* psz, IN INT dxColumn, IN INT dxColText OPTIONAL ) /* Returns a heap string containing the 'psz' shortened to fit in the ** given width, if necessary, by truncating and adding "...". 'Hdc' is the ** device context with the appropiate font selected. 'DxColumn' is the ** width of the column. It is caller's responsibility to Free the ** returned string. */ { const TCHAR szDots[] = TEXT("..."); SIZE size; TCHAR* pszResult; TCHAR* pszResultLast; TCHAR* pszResult2nd; DWORD cch; cch = lstrlen( psz ); pszResult = Malloc( (cch * sizeof(TCHAR)) + sizeof(szDots) ); if (!pszResult) return NULL; lstrcpy( pszResult, psz ); dxColumn -= dxColText; if (dxColumn <= 0) { /* None of the column text will be visible so bag the calculations and ** just return the original string. */ return pszResult; } if (!GetTextExtentPoint32( hdc, pszResult, cch, &size )) { Free( pszResult ); return NULL; } pszResult2nd = CharNext( pszResult ); pszResultLast = pszResult + cch; while (size.cx > dxColumn && pszResultLast > pszResult2nd) { /* Doesn't fit. Lop off a character, add the ellipsis, and try again. ** The minimum result is "..." for empty original or "x..." for ** non-empty original. */ pszResultLast = CharPrev( pszResult2nd, pszResultLast ); lstrcpy( pszResultLast, szDots ); if (!GetTextExtentPoint( hdc, pszResult, lstrlen( pszResult ), &size )) { Free( pszResult ); return NULL; } } return pszResult; } VOID ExpandWindow( IN HWND hwnd, IN LONG dx, IN LONG dy ) /* Expands window 'hwnd' 'dx' pels to the right and 'dy' pels down from ** it's current size. */ { RECT rect; GetWindowRect( hwnd, &rect ); SetWindowPos( hwnd, NULL, 0, 0, rect.right - rect.left + dx, rect.bottom - rect.top + dy, SWP_NOMOVE + SWP_NOZORDER ); } TCHAR* GetText( IN HWND hwnd ) /* Returns heap block containing the text contents of the window 'hwnd' or ** NULL. It is caller's responsibility to Free the returned string. */ { INT cch; TCHAR* psz; cch = GetWindowTextLength( hwnd ); psz = Malloc( (cch + 1) * sizeof(TCHAR) ); if (psz) { *psz = TEXT('\0'); GetWindowText( hwnd, psz, cch + 1 ); } return psz; } HWND HwndFromCursorPos( IN HINSTANCE hinstance, IN POINT* ppt OPTIONAL ) /* Returns a "Static" control window created at the specified position ** (or at the cursor position if NULL is passed in). ** The window is moved off the desktop using SetOffDesktop() ** so that it can be specified as the owner window ** for a popup dialog shown using MsgDlgUtil. ** The window returned should be destroyed using DestroyWindow(). */ { HWND hwnd; POINT pt; if (ppt) { pt = *ppt; } else { GetCursorPos(&pt); } // // create the window // hwnd = CreateWindowEx( WS_EX_TOOLWINDOW, TEXT("Static"), NULL, WS_POPUP, pt.x, pt.y, 1, 1, NULL, NULL, hinstance, NULL ); if (!hwnd) { return NULL; } // // move it off the desktop // SetOffDesktop(hwnd, SOD_MoveOff, NULL); ShowWindow(hwnd, SW_SHOW); return hwnd; } LPTSTR IpGetAddressAsText( HWND hwndIp ) { if (SendMessage( hwndIp, IPM_ISBLANK, 0, 0 )) { return NULL; } else { DWORD dwIpAddrHost; TCHAR szIpAddr [32]; SendMessage( hwndIp, IPM_GETADDRESS, 0, (LPARAM)&dwIpAddrHost ); IpHostAddrToPsz( dwIpAddrHost, szIpAddr ); return StrDup( szIpAddr ); } } void IpSetAddressText( HWND hwndIp, LPCTSTR pszIpAddress ) { if (pszIpAddress) { DWORD dwIpAddrHost = IpPszToHostAddr( pszIpAddress ); SendMessage( hwndIp, IPM_SETADDRESS, 0, dwIpAddrHost ); } else { SendMessage( hwndIp, IPM_CLEARADDRESS, 0, 0 ); } } INT ListBox_AddItem( IN HWND hwndLb, IN TCHAR* pszText, IN VOID* pItem ) /* Adds data item 'pItem' with displayed text 'pszText' to listbox ** 'hwndLb'. The item is added sorted if the listbox has LBS_SORT style, ** or to the end of the list otherwise. If the listbox has LB_HASSTRINGS ** style, 'pItem' is a null terminated string, otherwise it is any user ** defined data. ** ** Returns the index of the item in the list or negative if error. */ { INT nIndex; nIndex = ListBox_AddString( hwndLb, pszText ); if (nIndex >= 0) ListBox_SetItemData( hwndLb, nIndex, pItem ); return nIndex; } TCHAR* ListBox_GetPsz( IN HWND hwnd, IN INT nIndex ) /* Returns heap block containing the text contents of the 'nIndex'th item ** of list box 'hwnd' or NULL. It is caller's responsibility to Free the ** returned string. */ { INT cch; TCHAR* psz; cch = ListBox_GetTextLen( hwnd, nIndex ); psz = Malloc( (cch + 1) * sizeof(TCHAR) ); if (psz) { *psz = TEXT('\0'); ListBox_GetText( hwnd, nIndex, psz ); } return psz; } INT ListBox_IndexFromString( IN HWND hwnd, IN TCHAR* psz ) /* Returns the index of the item in string list 'hwnd' that matches 'psz' ** or -1 if not found. Unlike, ListBox_FindStringExact, this compare in ** case sensitive. */ { INT i; INT c; c = ListBox_GetCount( hwnd ); for (i = 0; i < c; ++i) { TCHAR* pszThis; pszThis = ListBox_GetPsz( hwnd, i ); if (pszThis) { BOOL f; f = (lstrcmp( psz, pszThis ) == 0); Free( pszThis ); if (f) return i; } } return -1; } VOID ListBox_SetCurSelNotify( IN HWND hwndLb, IN INT nIndex ) /* Set selection in listbox 'hwndLb' to 'nIndex' and notify parent as if ** user had clicked the item which Windows doesn't do for some reason. */ { ListBox_SetCurSel( hwndLb, nIndex ); SendMessage( GetParent( hwndLb ), WM_COMMAND, (WPARAM )MAKELONG( (WORD )GetDlgCtrlID( hwndLb ), (WORD )LBN_SELCHANGE ), (LPARAM )hwndLb ); } VOID* ListView_GetParamPtr( IN HWND hwndLv, IN INT iItem ) /* Returns the lParam address of the 'iItem' item in 'hwndLv' or NULL if ** none or error. */ { LV_ITEM item; ZeroMemory( &item, sizeof(item) ); item.mask = LVIF_PARAM; item.iItem = iItem; if (!ListView_GetItem( hwndLv, &item )) return NULL; return (VOID* )item.lParam; } VOID* ListView_GetSelectedParamPtr( IN HWND hwndLv ) /* Returns the lParam address of the first selected item in 'hwndLv' or ** NULL if none or error. */ { INT iSel; LV_ITEM item; iSel = ListView_GetNextItem( hwndLv, -1, LVNI_SELECTED ); if (iSel < 0) return NULL; return ListView_GetParamPtr( hwndLv, iSel ); } VOID ListView_InsertSingleAutoWidthColumn( HWND hwndLv ) // Insert a single auto-sized column into listview 'hwndLv', e.g. for a // list of checkboxes with no visible column header. // { LV_COLUMN col; ZeroMemory( &col, sizeof(col) ); col.mask = LVCF_FMT; col.fmt = LVCFMT_LEFT; ListView_InsertColumn( hwndLv, 0, &col ); ListView_SetColumnWidth( hwndLv, 0, LVSCW_AUTOSIZE ); } BOOL ListView_SetParamPtr( IN HWND hwndLv, IN INT iItem, IN VOID* pParam ) /* Set the lParam address of the 'iItem' item in 'hwndLv' to 'pParam'. ** Return true if successful, false otherwise. */ { LV_ITEM item; ZeroMemory( &item, sizeof(item) ); item.mask = LVIF_PARAM; item.iItem = iItem; item.lParam = (LPARAM )pParam; return ListView_SetItem( hwndLv, &item ); } VOID Menu_CreateAccelProxies( IN HINSTANCE hinst, IN HWND hwndParent, IN DWORD dwMid ) /* Causes accelerators on a popup menu to choose menu commands when the ** popup menu is not dropped down. 'Hinst' is the app/dll instance. ** 'HwndParent' is the window that receives the popup menu command ** messages. 'DwMid' is the menu ID of the menu bar containing the popup ** menu. */ { #define MCF_cbBuf 512 HMENU hmenuBar; HMENU hmenuPopup; TCHAR szBuf[ MCF_cbBuf ]; MENUITEMINFO item; INT i; hmenuBar = LoadMenu( hinst, MAKEINTRESOURCE( dwMid ) ); ASSERT(hmenuBar); hmenuPopup = GetSubMenu( hmenuBar, 0 ); ASSERT(hmenuPopup); /* Loop thru menu items on the popup menu. */ for (i = 0; TRUE; ++i) { ZeroMemory( &item, sizeof(item) ); item.cbSize = sizeof(item); item.fMask = MIIM_TYPE + MIIM_ID; item.dwTypeData = szBuf; item.cch = MCF_cbBuf; if (!GetMenuItemInfo( hmenuPopup, i, TRUE, &item )) break; if (item.fType != MFT_STRING) continue; /* Create an off-screen button on the parent with the same ID and ** text as the menu item. */ CreateWindow( TEXT("button"), szBuf, WS_CHILD, 30000, 30000, 0, 0, hwndParent, (HMENU )UlongToPtr(item.wID), hinst, NULL ); } DestroyMenu( hmenuBar ); } VOID ScreenToClientRect( IN HWND hwnd, IN OUT RECT* pRect ) /* Converts '*pRect' from screen to client coordinates of 'hwnd'. */ { POINT xyUL; POINT xyBR; xyUL.x = pRect->left; xyUL.y = pRect->top; xyBR.x = pRect->right; xyBR.y = pRect->bottom; ScreenToClient( hwnd, &xyUL ); ScreenToClient( hwnd, &xyBR ); pRect->left = xyUL.x; pRect->top = xyUL.y; pRect->right = xyBR.x; pRect->bottom = xyBR.y; } BOOL SetOffDesktop( IN HWND hwnd, IN DWORD dwAction, OUT RECT* prectOrg ) /* Move 'hwnd' back and forth from the visible desktop to the area ** off-screen. Use this when you want to "hide" your owner window without ** hiding yourself which Windows automatically does. ** ** 'dwAction' describes the action to take: ** SOD_Moveoff: Move 'hwnd' off the desktop. ** SOD_MoveBackFree: Undo SOD_MoveOff. ** SOD_GetOrgRect: Retrieves original 'hwnd' position. ** SOD_Free: Free SOD_MoveOff context without restoring. ** SOD_MoveBackHidden: Move window back, but hidden so you can call ** routines that internally query the position ** of the window. Undo with SOD_Moveoff. ** ** '*prectOrg' is set to the original window position when 'dwAction' is ** SOD_GetOrgRect. Otherwise, it is ignored, and may be NULL. ** ** Returns true if the window has a SODINFO context, false otherwise. */ { SODINFO* pInfo; TRACE2("SetOffDesktop(h=$%08x,a=%d)",hwnd,dwAction); if (!g_SodContextId) { g_SodContextId = (LPCTSTR )GlobalAddAtom( TEXT("RASSETOFFDESKTOP") ); if (!g_SodContextId) return FALSE; } pInfo = (SODINFO* )GetProp( hwnd, g_SodContextId ); if (dwAction == SOD_MoveOff) { if (pInfo) { /* Caller is undoing a SOD_MoveBackHidden. */ SetWindowPos( hwnd, NULL, pInfo->rectOrg.left, GetSystemMetrics( SM_CYSCREEN ), 0, 0, SWP_NOSIZE + SWP_NOZORDER ); if (pInfo->fWeMadeInvisible) { ShowWindow( hwnd, SW_SHOW ); pInfo->fWeMadeInvisible = FALSE; } } else { BOOL f; pInfo = (SODINFO* )Malloc( sizeof(SODINFO) ); if (!pInfo) return FALSE; f = IsWindowVisible( hwnd ); if (!f) ShowWindow( hwnd, SW_HIDE ); GetWindowRect( hwnd, &pInfo->rectOrg ); SetWindowPos( hwnd, NULL, pInfo->rectOrg.left, GetSystemMetrics( SM_CYSCREEN ), 0, 0, SWP_NOSIZE + SWP_NOZORDER ); if (!f) ShowWindow( hwnd, SW_SHOW ); pInfo->fWeMadeInvisible = FALSE; SetProp( hwnd, g_SodContextId, pInfo ); } } else if (dwAction == SOD_MoveBackFree || dwAction == SOD_Free) { if (pInfo) { if (dwAction == SOD_MoveBackFree) { SetWindowPos( hwnd, NULL, pInfo->rectOrg.left, pInfo->rectOrg.top, 0, 0, SWP_NOSIZE + SWP_NOZORDER ); } Free( pInfo ); RemoveProp( hwnd, g_SodContextId ); } return FALSE; } else if (dwAction == SOD_GetOrgRect) { if (!pInfo) return FALSE; *prectOrg = pInfo->rectOrg; } else { ASSERT(dwAction==SOD_MoveBackHidden); if (!pInfo) return FALSE; if (IsWindowVisible( hwnd )) { ShowWindow( hwnd, SW_HIDE ); pInfo->fWeMadeInvisible = TRUE; } SetWindowPos( hwnd, NULL, pInfo->rectOrg.left, pInfo->rectOrg.top, 0, 0, SWP_NOSIZE + SWP_NOZORDER ); } return TRUE; } BOOL SetDlgItemNum( IN HWND hwndDlg, IN INT iDlgItem, IN UINT uValue ) /* Similar to SetDlgItemInt, but this function uses commas (or the ** locale-specific separator) to delimit thousands */ { DWORD dwSize; TCHAR szNumber[32]; dwSize = 30; GetNumberString(uValue, szNumber, &dwSize); return SetDlgItemText(hwndDlg, iDlgItem, szNumber); } BOOL SetEvenTabWidths( IN HWND hwndDlg, IN DWORD cPages ) /* Set the tabs on property sheet 'hwndDlg' to have even fixed width. ** 'cPages' is the number of pages on the property sheet. ** ** Returns true if successful, false if any of the tabs requires more than ** the fixed width in which case the call has no effect. */ { HWND hwndTab; LONG lStyle; RECT rect; INT dxFixed; /* The tab control uses hard-coded 1-inch tabs when you set FIXEDWIDTH ** style while we want the tabs to fill the page, so we figure out the ** correct width ourselves. For some reason, without a fudge-factor (the ** -10) the expansion does not fit on a single line. The factor ** absolutely required varies between large and small fonts and the number ** of tabs does not seem to be a factor. */ hwndTab = PropSheet_GetTabControl( hwndDlg ); GetWindowRect( hwndTab, &rect ); dxFixed = (rect.right - rect.left - 10 ) / cPages; while (cPages > 0) { RECT rectTab; --cPages; if (!TabCtrl_GetItemRect( hwndTab, cPages, &rectTab ) || dxFixed < rectTab.right - rectTab.left) { /* This tab requires more than the fixed width. Since the fixed ** width is unworkable do nothing. */ return FALSE; } } TabCtrl_SetItemSize( hwndTab, dxFixed, 0 ); lStyle = GetWindowLong( hwndTab, GWL_STYLE ); SetWindowLong( hwndTab, GWL_STYLE, lStyle | TCS_FIXEDWIDTH ); return TRUE; } HFONT SetFont( HWND hwndCtrl, TCHAR* pszFaceName, BYTE bfPitchAndFamily, INT nPointSize, BOOL fUnderline, BOOL fStrikeout, BOOL fItalic, BOOL fBold ) /* Sets font of control 'hwndCtrl' to a font matching the specified ** attributes. See LOGFONT documentation. ** ** Returns the HFONT of the created font if successful or NULL. Caller ** should DeleteObject the returned HFONT when the control is destroyed. */ { LOGFONT logfont; INT nPelsPerInch; HFONT hfont; { HDC hdc = GetDC( NULL ); if (hdc == NULL) { return NULL; } nPelsPerInch = GetDeviceCaps( hdc, LOGPIXELSY ); ReleaseDC( NULL, hdc ); } ZeroMemory( &logfont, sizeof(logfont) ); logfont.lfHeight = -MulDiv( nPointSize, nPelsPerInch, 72 ); { DWORD cp; CHARSETINFO csi; cp = GetACP(); if (TranslateCharsetInfo( &cp, &csi, TCI_SRCCODEPAGE )) logfont.lfCharSet = (UCHAR)csi.ciCharset; else logfont.lfCharSet = ANSI_CHARSET; } logfont.lfOutPrecision = OUT_DEFAULT_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.lfQuality = DEFAULT_QUALITY; logfont.lfPitchAndFamily = bfPitchAndFamily; lstrcpy( logfont.lfFaceName, pszFaceName ); logfont.lfUnderline = (BYTE)fUnderline; logfont.lfStrikeOut = (BYTE)fStrikeout; logfont.lfItalic = (BYTE)fItalic; logfont.lfWeight = (fBold) ? FW_BOLD : FW_NORMAL; hfont = CreateFontIndirect( &logfont ); if (hfont) { SendMessage( hwndCtrl, WM_SETFONT, (WPARAM )hfont, MAKELPARAM( TRUE, 0 ) ); } return hfont; } VOID SlideWindow( IN HWND hwnd, IN HWND hwndParent, IN LONG dx, IN LONG dy ) /* Moves window 'hwnd' 'dx' pels right and 'dy' pels down from it's ** current position. 'HwndParent' is the handle of 'hwnd's parent or NULL ** if 'hwnd' is not a child window. */ { RECT rect; POINT xy; GetWindowRect( hwnd, &rect ); xy.x = rect.left; xy.y = rect.top; if (GetParent( hwnd )) { /* * For mirrored parents we us the right point not the left. */ if (GetWindowLongPtr(GetParent( hwnd ), GWL_EXSTYLE) & WS_EX_LAYOUTRTL) { xy.x = rect.right; } ScreenToClient( hwndParent, &xy ); } SetWindowPos( hwnd, NULL, xy.x + dx, xy.y + dy, 0, 0, SWP_NOSIZE + SWP_NOZORDER ); } VOID UnclipWindow( IN HWND hwnd ) /* Moves window 'hwnd' so any clipped parts are again visible on the ** screen. The window is moved only as far as necessary to achieve this. */ { RECT rect; INT dxScreen = GetSystemMetrics( SM_CXSCREEN ); INT dyScreen = GetSystemMetrics( SM_CYSCREEN ); GetWindowRect( hwnd, &rect ); if (rect.right > dxScreen) rect.left = dxScreen - (rect.right - rect.left); if (rect.left < 0) rect.left = 0; if (rect.bottom > dyScreen) rect.top = dyScreen - (rect.bottom - rect.top); if (rect.top < 0) rect.top = 0; SetWindowPos( hwnd, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER + SWP_NOSIZE ); }