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