1/////////////////////////////////////////////////////////////////////////////// 2// Name: src/msw/ownerdrw.cpp 3// Purpose: implementation of wxOwnerDrawn class 4// Author: Vadim Zeitlin 5// Modified by: 6// Created: 13.11.97 7// RCS-ID: $Id: ownerdrw.cpp 60531 2009-05-06 16:04:20Z PC $ 8// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> 9// Licence: wxWindows licence 10/////////////////////////////////////////////////////////////////////////////// 11 12// For compilers that support precompilation, includes "wx.h". 13#include "wx/wxprec.h" 14 15#ifdef __BORLANDC__ 16 #pragma hdrstop 17#endif 18 19#ifndef WX_PRECOMP 20 #include "wx/window.h" 21 #include "wx/msw/private.h" 22 #include "wx/font.h" 23 #include "wx/bitmap.h" 24 #include "wx/image.h" 25 #include "wx/dcmemory.h" 26 #include "wx/menu.h" 27 #include "wx/utils.h" 28 #include "wx/settings.h" 29 #include "wx/menuitem.h" 30 #include "wx/module.h" 31#endif 32 33#include "wx/ownerdrw.h" 34#include "wx/fontutil.h" 35 36#if wxUSE_OWNER_DRAWN 37 38#ifndef SPI_GETKEYBOARDCUES 39#define SPI_GETKEYBOARDCUES 0x100A 40#endif 41 42class wxMSWSystemMenuFontModule : public wxModule 43{ 44public: 45 46 virtual bool OnInit() 47 { 48 ms_systemMenuFont = new wxFont; 49 50#if defined(__WXMSW__) && defined(__WIN32__) && defined(SM_CXMENUCHECK) 51 NONCLIENTMETRICS nm; 52 nm.cbSize = sizeof(NONCLIENTMETRICS); 53 if ( !::SystemParametersInfo(SPI_GETNONCLIENTMETRICS,0,&nm,0) ) 54 { 55#if WINVER >= 0x0600 56 // a new field has been added to NONCLIENTMETRICS under Vista, so 57 // the call to SystemParametersInfo() fails if we use the struct 58 // size incorporating this new value on an older system -- retry 59 // without it 60 nm.cbSize -= sizeof(int); 61 if ( !::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &nm, 0) ) 62#endif // WINVER >= 0x0600 63 { 64 // maybe we should initialize the struct with some defaults? 65 wxLogLastError(_T("SystemParametersInfo(SPI_GETNONCLIENTMETRICS)")); 66 } 67 } 68 69 ms_systemMenuButtonWidth = nm.iMenuHeight; 70 ms_systemMenuHeight = nm.iMenuHeight; 71 72 // create menu font 73 wxNativeFontInfo info; 74 memcpy(&info.lf, &nm.lfMenuFont, sizeof(LOGFONT)); 75 ms_systemMenuFont->Create(info); 76 77 if (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &ms_showCues, 0) == 0) 78 ms_showCues = true; 79#endif 80 81 return true; 82 } 83 84 virtual void OnExit() 85 { 86 delete ms_systemMenuFont; 87 ms_systemMenuFont = NULL; 88 } 89 90 static wxFont* ms_systemMenuFont; 91 static int ms_systemMenuButtonWidth; // windows clean install default 92 static int ms_systemMenuHeight; // windows clean install default 93 static bool ms_showCues; 94private: 95 DECLARE_DYNAMIC_CLASS(wxMSWSystemMenuFontModule) 96}; 97 98// these static variables are from the wxMSWSystemMenuFontModule object 99// and reflect the system settings returned by the Win32 API's 100// SystemParametersInfo() call. 101 102wxFont* wxMSWSystemMenuFontModule::ms_systemMenuFont = NULL; 103int wxMSWSystemMenuFontModule::ms_systemMenuButtonWidth = 18; // windows clean install default 104int wxMSWSystemMenuFontModule::ms_systemMenuHeight = 18; // windows clean install default 105bool wxMSWSystemMenuFontModule::ms_showCues = true; 106 107IMPLEMENT_DYNAMIC_CLASS(wxMSWSystemMenuFontModule, wxModule) 108 109 110// VC++ 6 gives a warning here: 111// 112// return type for 'OwnerDrawnSet_wxImplementation_HashTable::iterator:: 113// operator ->' is 'class wxOwnerDrawn ** ' (ie; not a UDT or reference to 114// a UDT. Will produce errors if applied using infix notation. 115// 116// shut it down 117#if defined __VISUALC__ && __VISUALC__ <= 1300 118 #if __VISUALC__ >= 1200 119 #pragma warning(push) 120 #define POP_WARNINGS 121 #endif 122 #pragma warning(disable: 4284) 123#endif 124 125#include "wx/hashset.h" 126WX_DECLARE_HASH_SET(wxOwnerDrawn*, wxPointerHash, wxPointerEqual, OwnerDrawnSet); 127 128#ifdef POP_WARNINGS 129 #pragma warning(pop) 130#endif 131 132// ============================================================================ 133// implementation of wxOwnerDrawn class 134// ============================================================================ 135 136// ctor 137// ---- 138wxOwnerDrawn::wxOwnerDrawn(const wxString& str, 139 bool bCheckable, 140 bool bMenuItem) 141 : m_strName(str) 142{ 143 if ( ms_nDefaultMarginWidth == 0 ) 144 { 145 ms_nDefaultMarginWidth = ::GetSystemMetrics(SM_CXMENUCHECK) + 146 wxSystemSettings::GetMetric(wxSYS_EDGE_X); 147 ms_nLastMarginWidth = ms_nDefaultMarginWidth; 148 } 149 150 m_bCheckable = bCheckable; 151 m_bOwnerDrawn = false; 152 m_isMenuItem = bMenuItem; 153 m_nHeight = 0; 154 m_nMarginWidth = ms_nLastMarginWidth; 155 m_nMinHeight = wxMSWSystemMenuFontModule::ms_systemMenuHeight; 156} 157 158wxOwnerDrawn::~wxOwnerDrawn() 159{ 160} 161 162bool wxOwnerDrawn::IsMenuItem() const 163{ 164 return m_isMenuItem; 165} 166 167 168// these items will be set during the first invocation of the c'tor, 169// because the values will be determined by checking the system settings, 170// which is a chunk of code 171size_t wxOwnerDrawn::ms_nDefaultMarginWidth = 0; 172size_t wxOwnerDrawn::ms_nLastMarginWidth = 0; 173 174 175// drawing 176// ------- 177 178wxFont wxOwnerDrawn::GetFontToUse() const 179{ 180 wxFont font = m_font; 181 if ( !font.Ok() ) 182 { 183 if ( IsMenuItem() ) 184 font = *wxMSWSystemMenuFontModule::ms_systemMenuFont; 185 186 if ( !font.Ok() ) 187 font = *wxNORMAL_FONT; 188 } 189 190 return font; 191} 192 193// get size of the item 194// The item size includes the menu string, the accel string, 195// the bitmap and size for a submenu expansion arrow... 196bool wxOwnerDrawn::OnMeasureItem(size_t *pwidth, size_t *pheight) 197{ 198 if ( IsOwnerDrawn() ) 199 { 200 wxMemoryDC dc; 201 202 wxString str = wxStripMenuCodes(m_strName); 203 204 // if we have a valid accel string, then pad out 205 // the menu string so that the menu and accel string are not 206 // placed on top of each other. 207 if ( !m_strAccel.empty() ) 208 { 209 str.Pad(str.length()%8); 210 str += m_strAccel; 211 } 212 213 dc.SetFont(GetFontToUse()); 214 215 dc.GetTextExtent(str, (long *)pwidth, (long *)pheight); 216 217 // add space at the end of the menu for the submenu expansion arrow 218 // this will also allow offsetting the accel string from the right edge 219 *pwidth += GetMarginWidth() + 16; 220 } 221 else // don't draw the text, just the bitmap (if any) 222 { 223 *pwidth = 224 *pheight = 0; 225 } 226 227 // increase size to accommodate bigger bitmaps if necessary 228 if (m_bmpChecked.Ok()) 229 { 230 // Is BMP height larger then text height? 231 size_t adjustedHeight = m_bmpChecked.GetHeight() + 232 2*wxSystemSettings::GetMetric(wxSYS_EDGE_Y); 233 if (*pheight < adjustedHeight) 234 *pheight = adjustedHeight; 235 236 const size_t widthBmp = m_bmpChecked.GetWidth(); 237 if ( IsOwnerDrawn() ) 238 { 239 // widen the margin to fit the bitmap if necessary 240 if ((size_t)GetMarginWidth() < widthBmp) 241 SetMarginWidth(widthBmp); 242 } 243 else // we must allocate enough space for the bitmap 244 { 245 *pwidth += widthBmp; 246 } 247 } 248 249 // add a 4-pixel separator, otherwise menus look cluttered 250 *pwidth += 4; 251 252 // make sure that this item is at least as tall as the system menu height 253 if ( *pheight < m_nMinHeight ) 254 *pheight = m_nMinHeight; 255 256 // remember height for use in OnDrawItem 257 m_nHeight = *pheight; 258 259 return true; 260} 261 262// draw the item 263bool wxOwnerDrawn::OnDrawItem(wxDC& dc, 264 const wxRect& rc, 265 wxODAction, 266 wxODStatus st) 267{ 268 // this flag determines whether or not an edge will 269 // be drawn around the bitmap. In most "windows classic" 270 // applications, a 1-pixel highlight edge is drawn around 271 // the bitmap of an item when it is selected. However, 272 // with the new "luna" theme, no edge is drawn around 273 // the bitmap because the background is white (this applies 274 // only to "non-XP style" menus w/ bitmaps -- 275 // see IE 6 menus for an example) 276 277 bool draw_bitmap_edge = true; 278 279 // set the colors 280 // -------------- 281 DWORD colBack, colText; 282 if ( st & wxODSelected ) 283 { 284 colBack = GetSysColor(COLOR_HIGHLIGHT); 285 if (!(st & wxODDisabled)) 286 { 287 colText = GetSysColor(COLOR_HIGHLIGHTTEXT); 288 } 289 else 290 { 291 colText = GetSysColor(COLOR_GRAYTEXT); 292 } 293 } 294 else 295 { 296 // fall back to default colors if none explicitly specified 297 colBack = m_colBack.Ok() ? wxColourToPalRGB(m_colBack) 298 : GetSysColor(COLOR_MENU); 299 colText = m_colText.Ok() ? wxColourToPalRGB(m_colText) 300 : GetSysColor(COLOR_MENUTEXT); 301 } 302 303 if ( IsOwnerDrawn() ) 304 { 305 // don't draw an edge around the bitmap, if background is white ... 306 DWORD menu_bg_color = GetSysColor(COLOR_MENU); 307 if ( ( GetRValue( menu_bg_color ) >= 0xf0 && 308 GetGValue( menu_bg_color ) >= 0xf0 && 309 GetBValue( menu_bg_color ) >= 0xf0 ) 310 ) 311 { 312 draw_bitmap_edge = false; 313 } 314 } 315 else // edge doesn't look well with default Windows drawing 316 { 317 draw_bitmap_edge = false; 318 } 319 320 321 HDC hdc = GetHdcOf(dc); 322 COLORREF colOldText = ::SetTextColor(hdc, colText), 323 colOldBack = ::SetBkColor(hdc, colBack); 324 325 // *2, as in wxSYS_EDGE_Y 326 int margin = GetMarginWidth() + 2 * wxSystemSettings::GetMetric(wxSYS_EDGE_X); 327 328 // select the font and draw the text 329 // --------------------------------- 330 331 332 // determine where to draw and leave space for a check-mark. 333 // + 1 pixel to separate the edge from the highlight rectangle 334 int xText = rc.x + margin + 1; 335 336 337 // using native API because it recognizes '&' 338 if ( IsOwnerDrawn() ) 339 { 340 int nPrevMode = SetBkMode(hdc, TRANSPARENT); 341 AutoHBRUSH hbr(colBack); 342 SelectInHDC selBrush(hdc, hbr); 343 344 RECT rectFill = { rc.GetLeft(), rc.GetTop(), 345 rc.GetRight() + 1, rc.GetBottom() + 1 }; 346 347 if ( (st & wxODSelected) && m_bmpChecked.Ok() && draw_bitmap_edge ) 348 { 349 // only draw the highlight under the text, not under 350 // the bitmap or checkmark 351 rectFill.left = xText; 352 } 353 354 FillRect(hdc, &rectFill, hbr); 355 356 // use default font if no font set 357 wxFont fontToUse = GetFontToUse(); 358 SelectInHDC selFont(hdc, GetHfontOf(fontToUse)); 359 360 wxString strMenuText = m_strName.BeforeFirst('\t'); 361 362 xText += 3; // separate text from the highlight rectangle 363 364 SIZE sizeRect; 365 ::GetTextExtentPoint32(hdc, strMenuText.c_str(), strMenuText.length(), &sizeRect); 366 ::DrawState(hdc, NULL, NULL, 367 (LPARAM)strMenuText.c_str(), strMenuText.length(), 368 xText, rc.y + (int) ((rc.GetHeight()-sizeRect.cy)/2.0), // centre text vertically 369 rc.GetWidth()-margin, sizeRect.cy, 370 DST_PREFIXTEXT | 371 (((st & wxODDisabled) && !(st & wxODSelected)) ? DSS_DISABLED : 0) | 372 (((st & wxODHidePrefix) && !wxMSWSystemMenuFontModule::ms_showCues) ? 512 : 0)); // 512 == DSS_HIDEPREFIX 373 374 // ::SetTextAlign(hdc, TA_RIGHT) doesn't work with DSS_DISABLED or DSS_MONO 375 // as the last parameter in DrawState() (at least with Windows98). So we have 376 // to take care of right alignment ourselves. 377 if ( !m_strAccel.empty() ) 378 { 379 int accel_width, accel_height; 380 dc.GetTextExtent(m_strAccel, &accel_width, &accel_height); 381 // right align accel string with right edge of menu ( offset by the 382 // margin width ) 383 ::DrawState(hdc, NULL, NULL, 384 (LPARAM)m_strAccel.c_str(), m_strAccel.length(), 385 rc.GetWidth()-16-accel_width, rc.y+(int) ((rc.GetHeight()-sizeRect.cy)/2.0), 386 0, 0, 387 DST_TEXT | 388 (((st & wxODDisabled) && !(st & wxODSelected)) ? DSS_DISABLED : 0)); 389 } 390 391 (void)SetBkMode(hdc, nPrevMode); 392 } 393 394 395 // draw the bitmap 396 // --------------- 397 if ( IsCheckable() && !m_bmpChecked.Ok() ) 398 { 399 if ( st & wxODChecked ) 400 { 401 // what goes on: DrawFrameControl creates a b/w mask, 402 // then we copy it to screen to have right colors 403 404 // first create a monochrome bitmap in a memory DC 405 HDC hdcMem = CreateCompatibleDC(hdc); 406 HBITMAP hbmpCheck = CreateBitmap(margin, m_nHeight, 1, 1, 0); 407 SelectObject(hdcMem, hbmpCheck); 408 409 // then draw a check mark into it 410 RECT rect = { 0, 0, margin, m_nHeight }; 411 if ( m_nHeight > 0 ) 412 { 413 ::DrawFrameControl(hdcMem, &rect, DFC_MENU, DFCS_MENUCHECK); 414 } 415 416 // finally copy it to screen DC and clean up 417 BitBlt(hdc, rc.x, rc.y, margin, m_nHeight, hdcMem, 0, 0, SRCCOPY); 418 419 DeleteDC(hdcMem); 420 DeleteObject(hbmpCheck); 421 } 422 } 423 else 424 { 425 wxBitmap bmp; 426 427 if ( st & wxODDisabled ) 428 { 429 bmp = GetDisabledBitmap(); 430 } 431 432 if ( !bmp.Ok() ) 433 { 434 // for not checkable bitmaps we should always use unchecked one 435 // because their checked bitmap is not set 436 bmp = GetBitmap(!IsCheckable() || (st & wxODChecked)); 437 438#if wxUSE_IMAGE 439 if ( bmp.Ok() && st & wxODDisabled ) 440 { 441 // we need to grey out the bitmap as we don't have any specific 442 // disabled bitmap 443 wxImage imgGrey = bmp.ConvertToImage().ConvertToGreyscale(); 444 if ( imgGrey.Ok() ) 445 bmp = wxBitmap(imgGrey); 446 } 447#endif // wxUSE_IMAGE 448 } 449 450 if ( bmp.Ok() ) 451 { 452 wxMemoryDC dcMem(&dc); 453 dcMem.SelectObjectAsSource(bmp); 454 455 // center bitmap 456 int nBmpWidth = bmp.GetWidth(), 457 nBmpHeight = bmp.GetHeight(); 458 459 // there should be enough space! 460 wxASSERT((nBmpWidth <= rc.GetWidth()) && (nBmpHeight <= rc.GetHeight())); 461 462 int heightDiff = m_nHeight - nBmpHeight; 463 dc.Blit(rc.x + (margin - nBmpWidth) / 2, 464 rc.y + heightDiff / 2, 465 nBmpWidth, nBmpHeight, 466 &dcMem, 0, 0, wxCOPY, true /* use mask */); 467 468 if ( ( st & wxODSelected ) && !( st & wxODDisabled ) && draw_bitmap_edge ) 469 { 470 RECT rectBmp = { rc.GetLeft(), rc.GetTop(), 471 rc.GetLeft() + margin, 472 rc.GetTop() + m_nHeight }; 473 SetBkColor(hdc, colBack); 474 475 DrawEdge(hdc, &rectBmp, BDR_RAISEDINNER, BF_RECT); 476 } 477 } 478 } 479 480 ::SetTextColor(hdc, colOldText); 481 ::SetBkColor(hdc, colOldBack); 482 483 return true; 484} 485 486 487#endif // wxUSE_OWNER_DRAWN 488