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