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