]> git.saurik.com Git - wxWidgets.git/blob - src/msw/notebook.cpp
Enable variadic macros for VC9 and later.
[wxWidgets.git] / src / msw / notebook.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/notebook.cpp
3 // Purpose: implementation of wxNotebook
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.06.98
7 // RCS-ID: $Id$
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 #if wxUSE_NOTEBOOK
20
21 #include "wx/notebook.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
25 #include "wx/string.h"
26 #include "wx/dc.h"
27 #include "wx/log.h"
28 #include "wx/event.h"
29 #include "wx/app.h"
30 #include "wx/dcclient.h"
31 #include "wx/dcmemory.h"
32 #include "wx/control.h"
33 #include "wx/panel.h"
34 #endif // WX_PRECOMP
35
36 #include "wx/imaglist.h"
37 #include "wx/sysopt.h"
38
39 #include "wx/msw/private.h"
40 #include "wx/msw/dc.h"
41
42 #include <windowsx.h>
43 #include "wx/msw/winundef.h"
44
45 #if wxUSE_UXTHEME
46 #include "wx/msw/uxtheme.h"
47 #endif
48
49 // ----------------------------------------------------------------------------
50 // macros
51 // ----------------------------------------------------------------------------
52
53 // check that the page index is valid
54 #define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())
55
56 // you can set USE_NOTEBOOK_ANTIFLICKER to 0 for desktop Windows versions too
57 // to disable code whih results in flicker-less notebook redrawing at the
58 // expense of some extra GDI resource consumption
59 #ifdef __WXWINCE__
60 // notebooks are never resized under CE anyhow
61 #define USE_NOTEBOOK_ANTIFLICKER 0
62 #else
63 #define USE_NOTEBOOK_ANTIFLICKER 1
64 #endif
65
66 // ----------------------------------------------------------------------------
67 // constants
68 // ----------------------------------------------------------------------------
69
70 // This is a work-around for missing defines in gcc-2.95 headers
71 #ifndef TCS_RIGHT
72 #define TCS_RIGHT 0x0002
73 #endif
74
75 #ifndef TCS_VERTICAL
76 #define TCS_VERTICAL 0x0080
77 #endif
78
79 #ifndef TCS_BOTTOM
80 #define TCS_BOTTOM TCS_RIGHT
81 #endif
82
83 // ----------------------------------------------------------------------------
84 // global variables
85 // ----------------------------------------------------------------------------
86
87 #if USE_NOTEBOOK_ANTIFLICKER
88
89 // the pointer to standard spin button wnd proc
90 static WXFARPROC gs_wndprocNotebookSpinBtn = (WXFARPROC)NULL;
91
92 // the pointer to standard tab control wnd proc
93 static WXFARPROC gs_wndprocNotebook = (WXFARPROC)NULL;
94
95 LRESULT APIENTRY _EXPORT wxNotebookWndProc(HWND hwnd,
96 UINT message,
97 WPARAM wParam,
98 LPARAM lParam);
99
100 #endif // USE_NOTEBOOK_ANTIFLICKER
101
102 // ----------------------------------------------------------------------------
103 // global functions
104 // ----------------------------------------------------------------------------
105
106 static bool HasTroubleWithNonTopTabs()
107 {
108 const int verComCtl32 = wxApp::GetComCtl32Version();
109
110 // 600 is XP, 616 is Vista -- and both have a problem with tabs not on top
111 // (but don't just test for >= 600 as Microsoft might decide to fix it in
112 // later versions, who knows...)
113 return verComCtl32 >= 600 && verComCtl32 <= 616;
114 }
115
116 // ----------------------------------------------------------------------------
117 // event table
118 // ----------------------------------------------------------------------------
119
120 BEGIN_EVENT_TABLE(wxNotebook, wxBookCtrlBase)
121 EVT_SIZE(wxNotebook::OnSize)
122 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
123
124 #if USE_NOTEBOOK_ANTIFLICKER
125 EVT_ERASE_BACKGROUND(wxNotebook::OnEraseBackground)
126 EVT_PAINT(wxNotebook::OnPaint)
127 #endif // USE_NOTEBOOK_ANTIFLICKER
128 END_EVENT_TABLE()
129
130 // ============================================================================
131 // implementation
132 // ============================================================================
133
134 // ----------------------------------------------------------------------------
135 // wxNotebook construction
136 // ----------------------------------------------------------------------------
137
138 // common part of all ctors
139 void wxNotebook::Init()
140 {
141 #if wxUSE_UXTHEME
142 m_hbrBackground = NULL;
143 #endif // wxUSE_UXTHEME
144
145 #if USE_NOTEBOOK_ANTIFLICKER
146 m_hasSubclassedUpdown = false;
147 m_doneUpdateHack = false;
148 #endif // USE_NOTEBOOK_ANTIFLICKER
149 }
150
151 // default for dynamic class
152 wxNotebook::wxNotebook()
153 {
154 Init();
155 }
156
157 // the same arguments as for wxControl
158 wxNotebook::wxNotebook(wxWindow *parent,
159 wxWindowID id,
160 const wxPoint& pos,
161 const wxSize& size,
162 long style,
163 const wxString& name)
164 {
165 Init();
166
167 Create(parent, id, pos, size, style, name);
168 }
169
170 // Create() function
171 bool wxNotebook::Create(wxWindow *parent,
172 wxWindowID id,
173 const wxPoint& pos,
174 const wxSize& size,
175 long style,
176 const wxString& name)
177 {
178 if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
179 {
180 #if defined(__POCKETPC__)
181 style |= wxBK_BOTTOM | wxNB_FLAT;
182 #else
183 style |= wxBK_TOP;
184 #endif
185 }
186
187 #ifdef __WXWINCE__
188 // Not sure why, but without this style, there is no border
189 // around the notebook tabs.
190 if (style & wxNB_FLAT)
191 style |= wxBORDER_SUNKEN;
192 #endif
193
194 #if !wxUSE_UXTHEME
195 // ComCtl32 notebook tabs simply don't work unless they're on top if we
196 // have uxtheme, we can work around it later (after control creation), but
197 // if we have been compiled without uxtheme support, we have to clear those
198 // styles
199 if ( HasTroubleWithNonTopTabs() )
200 {
201 style &= ~(wxBK_BOTTOM | wxBK_LEFT | wxBK_RIGHT);
202 }
203 #endif //wxUSE_UXTHEME
204
205 #if defined(__WINE__) && wxUSE_UNICODE
206 LPCTSTR className = L"SysTabControl32";
207 #else
208 LPCTSTR className = WC_TABCONTROL;
209 #endif
210
211 #if USE_NOTEBOOK_ANTIFLICKER
212 // SysTabCtl32 class has natively CS_HREDRAW and CS_VREDRAW enabled and it
213 // causes horrible flicker when resizing notebook, so get rid of it by
214 // using a class without these styles (but otherwise identical to it)
215 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
216 {
217 static ClassRegistrar s_clsNotebook;
218 if ( !s_clsNotebook.IsInitialized() )
219 {
220 // get a copy of standard class and modify it
221 WNDCLASS wc;
222
223 if ( ::GetClassInfo(NULL, WC_TABCONTROL, &wc) )
224 {
225 gs_wndprocNotebook =
226 reinterpret_cast<WXFARPROC>(wc.lpfnWndProc);
227 wc.lpszClassName = wxT("_wx_SysTabCtl32");
228 wc.style &= ~(CS_HREDRAW | CS_VREDRAW);
229 wc.hInstance = wxGetInstance();
230 wc.lpfnWndProc = wxNotebookWndProc;
231 s_clsNotebook.Register(wc);
232 }
233 else
234 {
235 wxLogLastError(wxT("GetClassInfoEx(SysTabCtl32)"));
236 }
237 }
238
239 // use our custom class if available but fall back to the standard
240 // notebook if we failed to register it
241 if ( s_clsNotebook.IsRegistered() )
242 {
243 // it's ok to use c_str() here as the static s_clsNotebook object
244 // has sufficiently long lifetime
245 className = s_clsNotebook.GetName().c_str();
246 }
247 }
248 #endif // USE_NOTEBOOK_ANTIFLICKER
249
250 if ( !CreateControl(parent, id, pos, size, style | wxTAB_TRAVERSAL,
251 wxDefaultValidator, name) )
252 return false;
253
254 if ( !MSWCreateControl(className, wxEmptyString, pos, size) )
255 return false;
256
257 #if wxUSE_UXTHEME
258 if ( HasFlag(wxNB_NOPAGETHEME) ||
259 wxSystemOptions::IsFalse(wxT("msw.notebook.themed-background")) )
260 {
261 SetBackgroundColour(GetThemeBackgroundColour());
262 }
263 else // use themed background by default
264 {
265 // create backing store
266 UpdateBgBrush();
267 }
268
269 // comctl32.dll 6.0 doesn't support non-top tabs with visual styles (the
270 // control is simply not rendered correctly), so we disable themes
271 // if possible, otherwise we simply clear the styles.
272 if ( HasTroubleWithNonTopTabs() &&
273 (style & (wxBK_BOTTOM | wxBK_LEFT | wxBK_RIGHT)) )
274 {
275 // check if we use themes at all -- if we don't, we're still okay
276 if ( wxUxThemeEngine::GetIfActive() )
277 {
278 wxUxThemeEngine::GetIfActive()->SetWindowTheme(GetHwnd(), L"", L"");
279
280 // correct the background color for the new non-themed control
281 SetBackgroundColour(GetThemeBackgroundColour());
282 }
283 }
284 #endif // wxUSE_UXTHEME
285
286 // Undocumented hack to get flat notebook style
287 // In fact, we should probably only do this in some
288 // curcumstances, i.e. if we know we will have a border
289 // at the bottom (the tab control doesn't draw it itself)
290 #if defined(__POCKETPC__) || defined(__SMARTPHONE__)
291 if (HasFlag(wxNB_FLAT))
292 {
293 SendMessage(GetHwnd(), CCM_SETVERSION, COMCTL32_VERSION, 0);
294 if (!m_hasBgCol)
295 SetBackgroundColour(*wxWHITE);
296 }
297 #endif
298 return true;
299 }
300
301 WXDWORD wxNotebook::MSWGetStyle(long style, WXDWORD *exstyle) const
302 {
303 WXDWORD tabStyle = wxControl::MSWGetStyle(style, exstyle);
304
305 tabStyle |= WS_TABSTOP | TCS_TABS;
306
307 if ( style & wxNB_MULTILINE )
308 tabStyle |= TCS_MULTILINE;
309 if ( style & wxNB_FIXEDWIDTH )
310 tabStyle |= TCS_FIXEDWIDTH;
311
312 if ( style & wxBK_BOTTOM )
313 tabStyle |= TCS_RIGHT;
314 else if ( style & wxBK_LEFT )
315 tabStyle |= TCS_VERTICAL;
316 else if ( style & wxBK_RIGHT )
317 tabStyle |= TCS_VERTICAL | TCS_RIGHT;
318
319 return tabStyle;
320 }
321
322 wxNotebook::~wxNotebook()
323 {
324 #if wxUSE_UXTHEME
325 if ( m_hbrBackground )
326 ::DeleteObject((HBRUSH)m_hbrBackground);
327 #endif // wxUSE_UXTHEME
328 }
329
330 // ----------------------------------------------------------------------------
331 // wxNotebook accessors
332 // ----------------------------------------------------------------------------
333
334 size_t wxNotebook::GetPageCount() const
335 {
336 // consistency check
337 wxASSERT( (int)m_pages.Count() == TabCtrl_GetItemCount(GetHwnd()) );
338
339 return m_pages.Count();
340 }
341
342 int wxNotebook::GetRowCount() const
343 {
344 return TabCtrl_GetRowCount(GetHwnd());
345 }
346
347 int wxNotebook::SetSelection(size_t nPage)
348 {
349 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );
350
351 if ( m_selection == wxNOT_FOUND || nPage != (size_t)m_selection )
352 {
353 if ( SendPageChangingEvent(nPage) )
354 {
355 // program allows the page change
356 const int selectionOld = m_selection;
357
358 UpdateSelection(nPage);
359
360 TabCtrl_SetCurSel(GetHwnd(), nPage);
361
362 SendPageChangedEvent(selectionOld, nPage);
363 }
364 }
365
366 return m_selection;
367 }
368
369 void wxNotebook::UpdateSelection(int selNew)
370 {
371 if ( m_selection != wxNOT_FOUND )
372 m_pages[m_selection]->Show(false);
373
374 if ( selNew != wxNOT_FOUND )
375 {
376 wxNotebookPage *pPage = m_pages[selNew];
377 pPage->Show(true);
378 }
379
380 // Changing the page should give the focus to it but, as per bug report
381 // http://sf.net/tracker/index.php?func=detail&aid=1150659&group_id=9863&atid=109863,
382 // we should not set the focus to it directly since it erroneously
383 // selects radio buttons and breaks keyboard handling for a notebook's
384 // scroll buttons. So give focus to the notebook and not the page.
385
386 // but don't do this is the notebook is hidden
387 if ( ::IsWindowVisible(GetHwnd()) )
388 SetFocus();
389
390 m_selection = selNew;
391 }
392
393 int wxNotebook::ChangeSelection(size_t nPage)
394 {
395 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );
396
397 const int selOld = m_selection;
398
399 if ( m_selection == wxNOT_FOUND || nPage != (size_t)m_selection )
400 {
401 TabCtrl_SetCurSel(GetHwnd(), nPage);
402
403 UpdateSelection(nPage);
404 }
405
406 return selOld;
407 }
408
409 bool wxNotebook::SetPageText(size_t nPage, const wxString& strText)
410 {
411 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, wxT("notebook page out of range") );
412
413 TC_ITEM tcItem;
414 tcItem.mask = TCIF_TEXT;
415 tcItem.pszText = (wxChar *)strText.wx_str();
416
417 if ( !HasFlag(wxNB_MULTILINE) )
418 return TabCtrl_SetItem(GetHwnd(), nPage, &tcItem) != 0;
419
420 // multiline - we need to set new page size if a line is added or removed
421 int rows = GetRowCount();
422 bool ret = TabCtrl_SetItem(GetHwnd(), nPage, &tcItem) != 0;
423
424 if ( ret && rows != GetRowCount() )
425 {
426 const wxRect r = GetPageSize();
427 const size_t count = m_pages.Count();
428 for ( size_t page = 0; page < count; page++ )
429 m_pages[page]->SetSize(r);
430 }
431
432 return ret;
433 }
434
435 wxString wxNotebook::GetPageText(size_t nPage) const
436 {
437 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxEmptyString, wxT("notebook page out of range") );
438
439 wxChar buf[256];
440 TC_ITEM tcItem;
441 tcItem.mask = TCIF_TEXT;
442 tcItem.pszText = buf;
443 tcItem.cchTextMax = WXSIZEOF(buf);
444
445 wxString str;
446 if ( TabCtrl_GetItem(GetHwnd(), nPage, &tcItem) )
447 str = tcItem.pszText;
448
449 return str;
450 }
451
452 int wxNotebook::GetPageImage(size_t nPage) const
453 {
454 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );
455
456 TC_ITEM tcItem;
457 tcItem.mask = TCIF_IMAGE;
458
459 return TabCtrl_GetItem(GetHwnd(), nPage, &tcItem) ? tcItem.iImage
460 : wxNOT_FOUND;
461 }
462
463 bool wxNotebook::SetPageImage(size_t nPage, int nImage)
464 {
465 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, wxT("notebook page out of range") );
466
467 TC_ITEM tcItem;
468 tcItem.mask = TCIF_IMAGE;
469 tcItem.iImage = nImage;
470
471 return TabCtrl_SetItem(GetHwnd(), nPage, &tcItem) != 0;
472 }
473
474 void wxNotebook::SetImageList(wxImageList* imageList)
475 {
476 wxNotebookBase::SetImageList(imageList);
477
478 if ( imageList )
479 {
480 (void) TabCtrl_SetImageList(GetHwnd(), GetHimagelistOf(imageList));
481 }
482 }
483
484 // ----------------------------------------------------------------------------
485 // wxNotebook size settings
486 // ----------------------------------------------------------------------------
487
488 wxRect wxNotebook::GetPageSize() const
489 {
490 wxRect r;
491
492 RECT rc;
493 ::GetClientRect(GetHwnd(), &rc);
494
495 // This check is to work around a bug in TabCtrl_AdjustRect which will
496 // cause a crash on win2k or on XP with themes disabled if either
497 // wxNB_MULTILINE is used or tabs are placed on a side, if the rectangle
498 // is too small.
499 //
500 // The value of 20 is chosen arbitrarily but seems to work
501 if ( rc.right > 20 && rc.bottom > 20 )
502 {
503 TabCtrl_AdjustRect(GetHwnd(), false, &rc);
504
505 wxCopyRECTToRect(rc, r);
506 }
507
508 return r;
509 }
510
511 void wxNotebook::SetPageSize(const wxSize& size)
512 {
513 // transform the page size into the notebook size
514 RECT rc;
515 rc.left =
516 rc.top = 0;
517 rc.right = size.x;
518 rc.bottom = size.y;
519
520 TabCtrl_AdjustRect(GetHwnd(), true, &rc);
521
522 // and now set it
523 SetSize(rc.right - rc.left, rc.bottom - rc.top);
524 }
525
526 void wxNotebook::SetPadding(const wxSize& padding)
527 {
528 TabCtrl_SetPadding(GetHwnd(), padding.x, padding.y);
529 }
530
531 // Windows-only at present. Also, you must use the wxNB_FIXEDWIDTH
532 // style.
533 void wxNotebook::SetTabSize(const wxSize& sz)
534 {
535 ::SendMessage(GetHwnd(), TCM_SETITEMSIZE, 0, MAKELPARAM(sz.x, sz.y));
536 }
537
538 wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage) const
539 {
540 // we can't use TabCtrl_AdjustRect here because it only works for wxNB_TOP
541 wxSize sizeTotal = sizePage;
542
543 wxSize tabSize;
544 if ( GetPageCount() > 0 )
545 {
546 RECT rect;
547 TabCtrl_GetItemRect(GetHwnd(), 0, &rect);
548 tabSize.x = rect.right - rect.left;
549 tabSize.y = rect.bottom - rect.top;
550 }
551
552 const int rows = GetRowCount();
553
554 // add an extra margin in both directions
555 const int MARGIN = 8;
556 if ( IsVertical() )
557 {
558 sizeTotal.x += MARGIN;
559 sizeTotal.y += tabSize.y * rows + MARGIN;
560 }
561 else // horizontal layout
562 {
563 sizeTotal.x += tabSize.x * rows + MARGIN;
564 sizeTotal.y += MARGIN;
565 }
566
567 return sizeTotal;
568 }
569
570 void wxNotebook::AdjustPageSize(wxNotebookPage *page)
571 {
572 wxCHECK_RET( page, wxT("NULL page in wxNotebook::AdjustPageSize") );
573
574 const wxRect r = GetPageSize();
575 if ( !r.IsEmpty() )
576 {
577 page->SetSize(r);
578 }
579 }
580
581 // ----------------------------------------------------------------------------
582 // wxNotebook operations
583 // ----------------------------------------------------------------------------
584
585 // remove one page from the notebook, without deleting
586 wxNotebookPage *wxNotebook::DoRemovePage(size_t nPage)
587 {
588 wxNotebookPage *pageRemoved = wxNotebookBase::DoRemovePage(nPage);
589 if ( !pageRemoved )
590 return NULL;
591
592 // hide the removed page to maintain the invariant that only the
593 // selected page is visible and others are hidden:
594 pageRemoved->Show(false);
595
596 TabCtrl_DeleteItem(GetHwnd(), nPage);
597
598 if ( m_pages.IsEmpty() )
599 {
600 // no selection any more, the notebook becamse empty
601 m_selection = wxNOT_FOUND;
602 }
603 else // notebook still not empty
604 {
605 int selNew = TabCtrl_GetCurSel(GetHwnd());
606 if ( selNew != wxNOT_FOUND )
607 {
608 // No selection change, just refresh the current selection.
609 // Because it could be that the slection index changed
610 // we need to update it.
611 // Note: this does not mean the selection it self changed.
612 m_selection = selNew;
613 m_pages[m_selection]->Refresh();
614 }
615 else if (int(nPage) == m_selection)
616 {
617 // The selection was deleted.
618
619 // Determine new selection.
620 if (m_selection == int(GetPageCount()))
621 selNew = m_selection - 1;
622 else
623 selNew = m_selection;
624
625 // m_selection must be always valid so reset it before calling
626 // SetSelection()
627 m_selection = wxNOT_FOUND;
628 SetSelection(selNew);
629 }
630 else
631 {
632 wxFAIL; // Windows did not behave ok.
633 }
634 }
635
636 return pageRemoved;
637 }
638
639 // remove all pages
640 bool wxNotebook::DeleteAllPages()
641 {
642 size_t nPageCount = GetPageCount();
643 size_t nPage;
644 for ( nPage = 0; nPage < nPageCount; nPage++ )
645 delete m_pages[nPage];
646
647 m_pages.Clear();
648
649 TabCtrl_DeleteAllItems(GetHwnd());
650
651 m_selection = wxNOT_FOUND;
652
653 InvalidateBestSize();
654 return true;
655 }
656
657 // same as AddPage() but does it at given position
658 bool wxNotebook::InsertPage(size_t nPage,
659 wxNotebookPage *pPage,
660 const wxString& strText,
661 bool bSelect,
662 int imageId)
663 {
664 wxCHECK_MSG( pPage != NULL, false, wxT("NULL page in wxNotebook::InsertPage") );
665 wxCHECK_MSG( IS_VALID_PAGE(nPage) || nPage == GetPageCount(), false,
666 wxT("invalid index in wxNotebook::InsertPage") );
667
668 wxASSERT_MSG( pPage->GetParent() == this,
669 wxT("notebook pages must have notebook as parent") );
670
671 // add a new tab to the control
672 // ----------------------------
673
674 // init all fields to 0
675 TC_ITEM tcItem;
676 wxZeroMemory(tcItem);
677
678 // set the image, if any
679 if ( imageId != -1 )
680 {
681 tcItem.mask |= TCIF_IMAGE;
682 tcItem.iImage = imageId;
683 }
684
685 // and the text
686 if ( !strText.empty() )
687 {
688 tcItem.mask |= TCIF_TEXT;
689 tcItem.pszText = const_cast<wxChar *>(strText.wx_str());
690 }
691
692 // hide the page: unless it is selected, it shouldn't be shown (and if it
693 // is selected it will be shown later)
694 HWND hwnd = GetWinHwnd(pPage);
695 SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_VISIBLE);
696
697 // this updates internal flag too -- otherwise it would get out of sync
698 // with the real state
699 pPage->Show(false);
700
701
702 // fit the notebook page to the tab control's display area: this should be
703 // done before adding it to the notebook or TabCtrl_InsertItem() will
704 // change the notebooks size itself!
705 AdjustPageSize(pPage);
706
707 // finally do insert it
708 if ( TabCtrl_InsertItem(GetHwnd(), nPage, &tcItem) == -1 )
709 {
710 wxLogError(wxT("Can't create the notebook page '%s'."), strText.c_str());
711
712 return false;
713 }
714
715 // need to update the bg brush when the first page is added
716 // so the first panel gets the correct themed background
717 if ( m_pages.empty() )
718 {
719 #if wxUSE_UXTHEME
720 UpdateBgBrush();
721 #endif // wxUSE_UXTHEME
722 }
723
724 // succeeded: save the pointer to the page
725 m_pages.Insert(pPage, nPage);
726
727 // we may need to adjust the size again if the notebook size changed:
728 // normally this only happens for the first page we add (the tabs which
729 // hadn't been there before are now shown) but for a multiline notebook it
730 // can happen for any page at all as a new row could have been started
731 if ( m_pages.GetCount() == 1 || HasFlag(wxNB_MULTILINE) )
732 {
733 AdjustPageSize(pPage);
734 }
735
736 // now deal with the selection
737 // ---------------------------
738
739 // if the inserted page is before the selected one, we must update the
740 // index of the selected page
741 if ( int(nPage) <= m_selection )
742 {
743 // one extra page added
744 m_selection++;
745 }
746
747 DoSetSelectionAfterInsertion(nPage, bSelect);
748
749 InvalidateBestSize();
750
751 return true;
752 }
753
754 int wxNotebook::HitTest(const wxPoint& pt, long *flags) const
755 {
756 TC_HITTESTINFO hitTestInfo;
757 hitTestInfo.pt.x = pt.x;
758 hitTestInfo.pt.y = pt.y;
759 int item = TabCtrl_HitTest(GetHwnd(), &hitTestInfo);
760
761 if ( flags )
762 {
763 *flags = 0;
764
765 if ((hitTestInfo.flags & TCHT_NOWHERE) == TCHT_NOWHERE)
766 *flags |= wxBK_HITTEST_NOWHERE;
767 if ((hitTestInfo.flags & TCHT_ONITEM) == TCHT_ONITEM)
768 *flags |= wxBK_HITTEST_ONITEM;
769 if ((hitTestInfo.flags & TCHT_ONITEMICON) == TCHT_ONITEMICON)
770 *flags |= wxBK_HITTEST_ONICON;
771 if ((hitTestInfo.flags & TCHT_ONITEMLABEL) == TCHT_ONITEMLABEL)
772 *flags |= wxBK_HITTEST_ONLABEL;
773 if ( item == wxNOT_FOUND && GetPageSize().Contains(pt) )
774 *flags |= wxBK_HITTEST_ONPAGE;
775 }
776
777 return item;
778 }
779
780 // ----------------------------------------------------------------------------
781 // flicker-less notebook redraw
782 // ----------------------------------------------------------------------------
783
784 #if USE_NOTEBOOK_ANTIFLICKER
785
786 // wnd proc for the spin button
787 LRESULT APIENTRY _EXPORT wxNotebookSpinBtnWndProc(HWND hwnd,
788 UINT message,
789 WPARAM wParam,
790 LPARAM lParam)
791 {
792 if ( message == WM_ERASEBKGND )
793 return 0;
794
795 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebookSpinBtn,
796 hwnd, message, wParam, lParam);
797 }
798
799 LRESULT APIENTRY _EXPORT wxNotebookWndProc(HWND hwnd,
800 UINT message,
801 WPARAM wParam,
802 LPARAM lParam)
803 {
804 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebook,
805 hwnd, message, wParam, lParam);
806 }
807
808 void wxNotebook::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
809 {
810 // do nothing here
811 }
812
813 void wxNotebook::OnPaint(wxPaintEvent& WXUNUSED(event))
814 {
815 wxPaintDC dc(this);
816 wxMemoryDC memdc;
817 RECT rc;
818 ::GetClientRect(GetHwnd(), &rc);
819 wxBitmap bmp(rc.right, rc.bottom);
820 memdc.SelectObject(bmp);
821
822 const wxLayoutDirection dir = dc.GetLayoutDirection();
823 memdc.SetLayoutDirection(dir);
824
825 // if there is no special brush just use the solid background colour
826 #if wxUSE_UXTHEME
827 HBRUSH hbr = (HBRUSH)m_hbrBackground;
828 #else
829 HBRUSH hbr = 0;
830 #endif
831 wxBrush brush;
832 if ( !hbr )
833 {
834 brush = wxBrush(GetBackgroundColour());
835 hbr = GetHbrushOf(brush);
836 }
837
838 wxMSWDCImpl *impl = (wxMSWDCImpl*) memdc.GetImpl();
839
840 ::FillRect(GetHdcOf(*impl), &rc, hbr);
841
842 MSWDefWindowProc(WM_PAINT, (WPARAM)(impl->GetHDC()), 0);
843
844 // For some reason in RTL mode, source offset has to be -1, otherwise the
845 // right border (physical) remains unpainted.
846 const wxCoord ofs = dir == wxLayout_RightToLeft ? -1 : 0;
847 dc.Blit(ofs, 0, rc.right, rc.bottom, &memdc, ofs, 0);
848 }
849
850 #endif // USE_NOTEBOOK_ANTIFLICKER
851
852 // ----------------------------------------------------------------------------
853 // wxNotebook callbacks
854 // ----------------------------------------------------------------------------
855
856 void wxNotebook::OnSize(wxSizeEvent& event)
857 {
858 if ( GetPageCount() == 0 )
859 {
860 // Prevents droppings on resize, but does cause some flicker
861 // when there are no pages.
862 Refresh();
863 event.Skip();
864 return;
865 }
866 #ifndef __WXWINCE__
867 else
868 {
869 // Without this, we can sometimes get droppings at the edges
870 // of a notebook, for example a notebook in a splitter window.
871 // This needs to be reconciled with the RefreshRect calls
872 // at the end of this function, which weren't enough to prevent
873 // the droppings.
874
875 wxSize sz = GetClientSize();
876
877 // Refresh right side
878 wxRect rect(sz.x-4, 0, 4, sz.y);
879 RefreshRect(rect);
880
881 // Refresh bottom side
882 rect = wxRect(0, sz.y-4, sz.x, 4);
883 RefreshRect(rect);
884
885 // Refresh left side
886 rect = wxRect(0, 0, 4, sz.y);
887 RefreshRect(rect);
888 }
889 #endif // !__WXWINCE__
890
891 // fit all the notebook pages to the tab control's display area
892
893 RECT rc;
894 rc.left = rc.top = 0;
895 GetSize((int *)&rc.right, (int *)&rc.bottom);
896
897 // save the total size, we'll use it below
898 int widthNbook = rc.right - rc.left,
899 heightNbook = rc.bottom - rc.top;
900
901 // there seems to be a bug in the implementation of TabCtrl_AdjustRect(): it
902 // returns completely false values for multiline tab controls after the tabs
903 // are added but before getting the first WM_SIZE (off by ~50 pixels, see
904 //
905 // http://sf.net/tracker/index.php?func=detail&aid=645323&group_id=9863&atid=109863
906 //
907 // and the only work around I could find was this ugly hack... without it
908 // simply toggling the "multiline" checkbox in the notebook sample resulted
909 // in a noticeable page displacement
910 if ( HasFlag(wxNB_MULTILINE) )
911 {
912 // avoid an infinite recursion: we get another notification too!
913 static bool s_isInOnSize = false;
914
915 if ( !s_isInOnSize )
916 {
917 s_isInOnSize = true;
918 SendMessage(GetHwnd(), WM_SIZE, SIZE_RESTORED,
919 MAKELPARAM(rc.right, rc.bottom));
920 s_isInOnSize = false;
921 }
922
923 // The best size depends on the number of rows of tabs, which can
924 // change when the notepad is resized.
925 InvalidateBestSize();
926 }
927
928 #if wxUSE_UXTHEME
929 // background bitmap size has changed, update the brush using it too
930 UpdateBgBrush();
931 #endif // wxUSE_UXTHEME
932
933 TabCtrl_AdjustRect(GetHwnd(), false, &rc);
934
935 int width = rc.right - rc.left,
936 height = rc.bottom - rc.top;
937 size_t nCount = m_pages.Count();
938 for ( size_t nPage = 0; nPage < nCount; nPage++ ) {
939 wxNotebookPage *pPage = m_pages[nPage];
940 pPage->SetSize(rc.left, rc.top, width, height);
941 }
942
943
944 // unless we had already repainted everything, we now need to refresh
945 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
946 {
947 // invalidate areas not covered by pages
948 RefreshRect(wxRect(0, 0, widthNbook, rc.top), false);
949 RefreshRect(wxRect(0, rc.top, rc.left, height), false);
950 RefreshRect(wxRect(0, rc.bottom, widthNbook, heightNbook - rc.bottom),
951 false);
952 RefreshRect(wxRect(rc.right, rc.top, widthNbook - rc.right, height),
953 false);
954 }
955
956 #if USE_NOTEBOOK_ANTIFLICKER
957 // subclass the spin control used by the notebook to scroll pages to
958 // prevent it from flickering on resize
959 if ( !m_hasSubclassedUpdown )
960 {
961 // iterate over all child windows to find spin button
962 for ( HWND child = ::GetWindow(GetHwnd(), GW_CHILD);
963 child;
964 child = ::GetWindow(child, GW_HWNDNEXT) )
965 {
966 wxWindow *childWindow = wxFindWinFromHandle((WXHWND)child);
967
968 // see if it exists, if no wxWindow found then assume it's the spin
969 // btn
970 if ( !childWindow )
971 {
972 // subclass the spin button to override WM_ERASEBKGND
973 if ( !gs_wndprocNotebookSpinBtn )
974 gs_wndprocNotebookSpinBtn = (WXFARPROC)wxGetWindowProc(child);
975
976 wxSetWindowProc(child, wxNotebookSpinBtnWndProc);
977 m_hasSubclassedUpdown = true;
978 break;
979 }
980 }
981 }
982
983 // Probably because of the games we play above to avoid flicker sometimes
984 // the text controls inside notebook pages are not shown correctly (they
985 // don't have their borders) when the notebook is shown for the first time.
986 // It's not really clear why does this happen and maybe the bug is in
987 // wxTextCtrl itself and not here but updating the page when it's about to
988 // be shown doesn't cost much and works around the problem so do it here
989 // for now.
990 if ( !m_doneUpdateHack && IsShownOnScreen() )
991 {
992 m_doneUpdateHack = true;
993 wxWindow* const page = GetCurrentPage();
994 if ( page )
995 page->Update();
996 }
997 #endif // USE_NOTEBOOK_ANTIFLICKER
998
999 event.Skip();
1000 }
1001
1002 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
1003 {
1004 if ( event.IsWindowChange() ) {
1005 // change pages
1006 AdvanceSelection(event.GetDirection());
1007 }
1008 else {
1009 // we get this event in 3 cases
1010 //
1011 // a) one of our pages might have generated it because the user TABbed
1012 // out from it in which case we should propagate the event upwards and
1013 // our parent will take care of setting the focus to prev/next sibling
1014 //
1015 // or
1016 //
1017 // b) the parent panel wants to give the focus to us so that we
1018 // forward it to our selected page. We can't deal with this in
1019 // OnSetFocus() because we don't know which direction the focus came
1020 // from in this case and so can't choose between setting the focus to
1021 // first or last panel child
1022 //
1023 // or
1024 //
1025 // c) we ourselves (see MSWTranslateMessage) generated the event
1026 //
1027 wxWindow * const parent = GetParent();
1028
1029 // the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
1030 const bool isFromParent = event.GetEventObject() == (wxObject*) parent;
1031 const bool isFromSelf = event.GetEventObject() == (wxObject*) this;
1032 const bool isForward = event.GetDirection();
1033
1034 if ( isFromSelf && !isForward )
1035 {
1036 // focus is currently on notebook tab and should leave
1037 // it backwards (Shift-TAB)
1038 event.SetCurrentFocus(this);
1039 parent->HandleWindowEvent(event);
1040 }
1041 else if ( isFromParent || isFromSelf )
1042 {
1043 // no, it doesn't come from child, case (b) or (c): forward to a
1044 // page but only if entering notebook page (i.e. direction is
1045 // backwards (Shift-TAB) comething from out-of-notebook, or
1046 // direction is forward (TAB) from ourselves),
1047 if ( m_selection != wxNOT_FOUND &&
1048 (!event.GetDirection() || isFromSelf) )
1049 {
1050 // so that the page knows that the event comes from it's parent
1051 // and is being propagated downwards
1052 event.SetEventObject(this);
1053
1054 wxWindow *page = m_pages[m_selection];
1055 if ( !page->HandleWindowEvent(event) )
1056 {
1057 page->SetFocus();
1058 }
1059 //else: page manages focus inside it itself
1060 }
1061 else // otherwise set the focus to the notebook itself
1062 {
1063 SetFocus();
1064 }
1065 }
1066 else
1067 {
1068 // it comes from our child, case (a), pass to the parent, but only
1069 // if the direction is forwards. Otherwise set the focus to the
1070 // notebook itself. The notebook is always the 'first' control of a
1071 // page.
1072 if ( !isForward )
1073 {
1074 SetFocus();
1075 }
1076 else if ( parent )
1077 {
1078 event.SetCurrentFocus(this);
1079 parent->HandleWindowEvent(event);
1080 }
1081 }
1082 }
1083 }
1084
1085 #if wxUSE_UXTHEME
1086
1087 bool wxNotebook::DoDrawBackground(WXHDC hDC, wxWindow *child)
1088 {
1089 wxUxThemeHandle theme(child ? child : this, L"TAB");
1090 if ( !theme )
1091 return false;
1092
1093 // get the notebook client rect (we're not interested in drawing tabs
1094 // themselves)
1095 wxRect r = GetPageSize();
1096 if ( r.IsEmpty() )
1097 return false;
1098
1099 RECT rc;
1100 wxCopyRectToRECT(r, rc);
1101
1102 // map rect to the coords of the window we're drawing in
1103 if ( child )
1104 ::MapWindowPoints(GetHwnd(), GetHwndOf(child), (POINT *)&rc, 2);
1105
1106 // we have the content area (page size), but we need to draw all of the
1107 // background for it to be aligned correctly
1108 wxUxThemeEngine::Get()->GetThemeBackgroundExtent
1109 (
1110 theme,
1111 (HDC) hDC,
1112 9 /* TABP_PANE */,
1113 0,
1114 &rc,
1115 &rc
1116 );
1117 wxUxThemeEngine::Get()->DrawThemeBackground
1118 (
1119 theme,
1120 (HDC) hDC,
1121 9 /* TABP_PANE */,
1122 0,
1123 &rc,
1124 NULL
1125 );
1126
1127 return true;
1128 }
1129
1130 WXHBRUSH wxNotebook::QueryBgBitmap()
1131 {
1132 wxRect r = GetPageSize();
1133 if ( r.IsEmpty() )
1134 return 0;
1135
1136 WindowHDC hDC(GetHwnd());
1137 MemoryHDC hDCMem(hDC);
1138 CompatibleBitmap hBmp(hDC, r.x + r.width, r.y + r.height);
1139
1140 SelectInHDC selectBmp(hDCMem, hBmp);
1141
1142 if ( !DoDrawBackground((WXHDC)(HDC)hDCMem) )
1143 return 0;
1144
1145 return (WXHBRUSH)::CreatePatternBrush(hBmp);
1146 }
1147
1148 void wxNotebook::UpdateBgBrush()
1149 {
1150 if ( m_hbrBackground )
1151 ::DeleteObject((HBRUSH)m_hbrBackground);
1152
1153 if ( !m_hasBgCol && wxUxThemeEngine::GetIfActive() )
1154 {
1155 m_hbrBackground = QueryBgBitmap();
1156 }
1157 else // no themes or we've got user-defined solid colour
1158 {
1159 m_hbrBackground = NULL;
1160 }
1161 }
1162
1163 bool wxNotebook::MSWPrintChild(WXHDC hDC, wxWindow *child)
1164 {
1165 // solid background colour overrides themed background drawing
1166 if ( !UseBgCol() && DoDrawBackground(hDC, child) )
1167 return true;
1168
1169 // If we're using a solid colour (for example if we've switched off
1170 // theming for this notebook), paint it
1171 if (UseBgCol())
1172 {
1173 wxRect r = GetPageSize();
1174 if ( r.IsEmpty() )
1175 return false;
1176
1177 RECT rc;
1178 wxCopyRectToRECT(r, rc);
1179
1180 // map rect to the coords of the window we're drawing in
1181 if ( child )
1182 ::MapWindowPoints(GetHwnd(), GetHwndOf(child), (POINT *)&rc, 2);
1183
1184 wxBrush brush(GetBackgroundColour());
1185 HBRUSH hbr = GetHbrushOf(brush);
1186
1187 ::FillRect((HDC) hDC, &rc, hbr);
1188
1189 return true;
1190 }
1191
1192 return wxNotebookBase::MSWPrintChild(hDC, child);
1193 }
1194
1195 #endif // wxUSE_UXTHEME
1196
1197 // Windows only: attempts to get colour for UX theme page background
1198 wxColour wxNotebook::GetThemeBackgroundColour() const
1199 {
1200 #if wxUSE_UXTHEME
1201 if (wxUxThemeEngine::Get())
1202 {
1203 wxUxThemeHandle hTheme((wxNotebook*) this, L"TAB");
1204 if (hTheme)
1205 {
1206 // This is total guesswork.
1207 // See PlatformSDK\Include\Tmschema.h for values.
1208 // JACS: can also use 9 (TABP_PANE)
1209 COLORREF themeColor;
1210 bool success = (S_OK == wxUxThemeEngine::Get()->GetThemeColor(
1211 hTheme,
1212 10 /* TABP_BODY */,
1213 1 /* NORMAL */,
1214 3821 /* FILLCOLORHINT */,
1215 &themeColor));
1216 if (!success)
1217 return GetBackgroundColour();
1218
1219 /*
1220 [DS] Workaround for WindowBlinds:
1221 Some themes return a near black theme color using FILLCOLORHINT,
1222 this makes notebook pages have an ugly black background and makes
1223 text (usually black) unreadable. Retry again with FILLCOLOR.
1224
1225 This workaround potentially breaks appearance of some themes,
1226 but in practice it already fixes some themes.
1227 */
1228 if (themeColor == 1)
1229 {
1230 wxUxThemeEngine::Get()->GetThemeColor(
1231 hTheme,
1232 10 /* TABP_BODY */,
1233 1 /* NORMAL */,
1234 3802 /* FILLCOLOR */,
1235 &themeColor);
1236 }
1237
1238 wxColour colour = wxRGBToColour(themeColor);
1239
1240 // Under Vista, the tab background colour is reported incorrectly.
1241 // So for the default theme at least, hard-code the colour to something
1242 // that will blend in.
1243
1244 static int s_AeroStatus = -1;
1245 if (s_AeroStatus == -1)
1246 {
1247 WCHAR szwThemeFile[1024];
1248 WCHAR szwThemeColor[256];
1249 if (S_OK == wxUxThemeEngine::Get()->GetCurrentThemeName(szwThemeFile, 1024, szwThemeColor, 256, NULL, 0))
1250 {
1251 wxString themeFile(szwThemeFile), themeColor(szwThemeColor);
1252 if (themeFile.Find(wxT("Aero")) != -1 && themeColor == wxT("NormalColor"))
1253 s_AeroStatus = 1;
1254 else
1255 s_AeroStatus = 0;
1256 }
1257 else
1258 s_AeroStatus = 0;
1259 }
1260
1261 if (s_AeroStatus == 1)
1262 colour = wxColour(255, 255, 255);
1263
1264 return colour;
1265 }
1266 }
1267 #endif // wxUSE_UXTHEME
1268
1269 return GetBackgroundColour();
1270 }
1271
1272 // ----------------------------------------------------------------------------
1273 // wxNotebook base class virtuals
1274 // ----------------------------------------------------------------------------
1275
1276 #if wxUSE_CONSTRAINTS
1277
1278 // override these 2 functions to do nothing: everything is done in OnSize
1279
1280 void wxNotebook::SetConstraintSizes(bool WXUNUSED(recurse))
1281 {
1282 // don't set the sizes of the pages - their correct size is not yet known
1283 wxControl::SetConstraintSizes(false);
1284 }
1285
1286 bool wxNotebook::DoPhase(int WXUNUSED(nPhase))
1287 {
1288 return true;
1289 }
1290
1291 #endif // wxUSE_CONSTRAINTS
1292
1293 // ----------------------------------------------------------------------------
1294 // wxNotebook Windows message handlers
1295 // ----------------------------------------------------------------------------
1296
1297 bool wxNotebook::MSWOnScroll(int orientation, WXWORD nSBCode,
1298 WXWORD pos, WXHWND control)
1299 {
1300 // don't generate EVT_SCROLLWIN events for the WM_SCROLLs coming from the
1301 // up-down control
1302 if ( control )
1303 return false;
1304
1305 return wxNotebookBase::MSWOnScroll(orientation, nSBCode, pos, control);
1306 }
1307
1308 bool wxNotebook::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result)
1309 {
1310 wxBookCtrlEvent event(wxEVT_NULL, m_windowId);
1311
1312 NMHDR* hdr = (NMHDR *)lParam;
1313 switch ( hdr->code ) {
1314 case TCN_SELCHANGE:
1315 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
1316 break;
1317
1318 case TCN_SELCHANGING:
1319 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING);
1320 break;
1321
1322 default:
1323 return wxControl::MSWOnNotify(idCtrl, lParam, result);
1324 }
1325
1326 event.SetSelection(TabCtrl_GetCurSel(GetHwnd()));
1327 event.SetOldSelection(m_selection);
1328 event.SetEventObject(this);
1329 event.SetInt(idCtrl);
1330
1331 // Change the selection before generating the event as its handler should
1332 // already see the new page selected.
1333 if ( hdr->code == TCN_SELCHANGE )
1334 UpdateSelection(event.GetSelection());
1335
1336 bool processed = HandleWindowEvent(event);
1337 *result = !event.IsAllowed();
1338 return processed;
1339 }
1340
1341 #endif // wxUSE_NOTEBOOK