]> git.saurik.com Git - wxWidgets.git/blob - src/msw/notebook.cpp
wxPopupTransientWindow::Popup no longer captures the mouse, so do it in
[wxWidgets.git] / src / msw / notebook.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: 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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13 #pragma implementation "notebook.h"
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #if wxUSE_NOTEBOOK
24
25 // wxWidgets
26 #ifndef WX_PRECOMP
27 #include "wx/string.h"
28 #include "wx/dc.h"
29 #endif // WX_PRECOMP
30
31 #include "wx/log.h"
32 #include "wx/imaglist.h"
33 #include "wx/event.h"
34 #include "wx/control.h"
35 #include "wx/notebook.h"
36 #include "wx/app.h"
37 #include "wx/sysopt.h"
38 #include "wx/dcclient.h"
39 #include "wx/dcmemory.h"
40
41 #include "wx/msw/private.h"
42
43 #include <windowsx.h>
44
45 #include <commctrl.h>
46
47 #include "wx/msw/winundef.h"
48
49 #if wxUSE_UXTHEME
50 #include "wx/msw/uxtheme.h"
51 #endif
52
53 // ----------------------------------------------------------------------------
54 // macros
55 // ----------------------------------------------------------------------------
56
57 // check that the page index is valid
58 #define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())
59
60 // hide the ugly cast
61 #define m_hwnd (HWND)GetHWND()
62
63 #ifdef __WXWINCE__
64 #define USE_NOTEBOOK_ANTIFLICKER 0
65 #else
66 // Set this to 1 to compile anti-flicker code, which creates a potentially
67 // large bitmap for every paint event
68 #define USE_NOTEBOOK_ANTIFLICKER 0
69 #endif
70
71 // ----------------------------------------------------------------------------
72 // constants
73 // ----------------------------------------------------------------------------
74
75 // This is a work-around for missing defines in gcc-2.95 headers
76 #ifndef TCS_RIGHT
77 #define TCS_RIGHT 0x0002
78 #endif
79
80 #ifndef TCS_VERTICAL
81 #define TCS_VERTICAL 0x0080
82 #endif
83
84 #ifndef TCS_BOTTOM
85 #define TCS_BOTTOM TCS_RIGHT
86 #endif
87
88 // ----------------------------------------------------------------------------
89 // event table
90 // ----------------------------------------------------------------------------
91
92 #include <wx/listimpl.cpp>
93
94 WX_DEFINE_LIST( wxNotebookPageInfoList ) ;
95
96 DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED)
97 DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING)
98
99 BEGIN_EVENT_TABLE(wxNotebook, wxControl)
100 #if USE_NOTEBOOK_ANTIFLICKER
101 EVT_ERASE_BACKGROUND(wxNotebook::OnEraseBackground)
102 EVT_PAINT(wxNotebook::OnPaint)
103 #endif
104 EVT_NOTEBOOK_PAGE_CHANGED(-1, wxNotebook::OnSelChange)
105 EVT_SIZE(wxNotebook::OnSize)
106 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
107 END_EVENT_TABLE()
108
109 #if wxUSE_EXTENDED_RTTI
110 WX_DEFINE_FLAGS( wxNotebookStyle )
111
112 wxBEGIN_FLAGS( wxNotebookStyle )
113 // new style border flags, we put them first to
114 // use them for streaming out
115 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
116 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
117 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
118 wxFLAGS_MEMBER(wxBORDER_RAISED)
119 wxFLAGS_MEMBER(wxBORDER_STATIC)
120 wxFLAGS_MEMBER(wxBORDER_NONE)
121
122 // old style border flags
123 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
124 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
125 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
126 wxFLAGS_MEMBER(wxRAISED_BORDER)
127 wxFLAGS_MEMBER(wxSTATIC_BORDER)
128 wxFLAGS_MEMBER(wxBORDER)
129
130 // standard window styles
131 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
132 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
133 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
134 wxFLAGS_MEMBER(wxWANTS_CHARS)
135 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
136 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
137 wxFLAGS_MEMBER(wxVSCROLL)
138 wxFLAGS_MEMBER(wxHSCROLL)
139
140 wxFLAGS_MEMBER(wxNB_FIXEDWIDTH)
141 wxFLAGS_MEMBER(wxNB_LEFT)
142 wxFLAGS_MEMBER(wxNB_RIGHT)
143 wxFLAGS_MEMBER(wxNB_BOTTOM)
144 wxFLAGS_MEMBER(wxNB_NOPAGETHEME)
145 wxFLAGS_MEMBER(wxNB_FLAT)
146
147 wxEND_FLAGS( wxNotebookStyle )
148
149 IMPLEMENT_DYNAMIC_CLASS_XTI(wxNotebook, wxControl,"wx/notebook.h")
150 IMPLEMENT_DYNAMIC_CLASS_XTI(wxNotebookPageInfo, wxObject , "wx/notebook.h" )
151
152 wxCOLLECTION_TYPE_INFO( wxNotebookPageInfo * , wxNotebookPageInfoList ) ;
153
154 template<> void wxCollectionToVariantArray( wxNotebookPageInfoList const &theList, wxxVariantArray &value)
155 {
156 wxListCollectionToVariantArray<wxNotebookPageInfoList::compatibility_iterator>( theList , value ) ;
157 }
158
159 wxBEGIN_PROPERTIES_TABLE(wxNotebook)
160 wxEVENT_PROPERTY( PageChanging , wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING , wxNotebookEvent )
161 wxEVENT_PROPERTY( PageChanged , wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED , wxNotebookEvent )
162
163 wxPROPERTY_COLLECTION( PageInfos , wxNotebookPageInfoList , wxNotebookPageInfo* , AddPageInfo , GetPageInfos , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
164 wxPROPERTY_FLAGS( WindowStyle , wxNotebookStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
165 wxEND_PROPERTIES_TABLE()
166
167 wxBEGIN_HANDLERS_TABLE(wxNotebook)
168 wxEND_HANDLERS_TABLE()
169
170 wxCONSTRUCTOR_5( wxNotebook , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle)
171
172
173 wxBEGIN_PROPERTIES_TABLE(wxNotebookPageInfo)
174 wxREADONLY_PROPERTY( Page , wxNotebookPage* , GetPage , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
175 wxREADONLY_PROPERTY( Text , wxString , GetText , wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
176 wxREADONLY_PROPERTY( Selected , bool , GetSelected , false, 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
177 wxREADONLY_PROPERTY( ImageId , int , GetImageId , -1 , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
178 wxEND_PROPERTIES_TABLE()
179
180 wxBEGIN_HANDLERS_TABLE(wxNotebookPageInfo)
181 wxEND_HANDLERS_TABLE()
182
183 wxCONSTRUCTOR_4( wxNotebookPageInfo , wxNotebookPage* , Page , wxString , Text , bool , Selected , int , ImageId )
184
185 #else
186 IMPLEMENT_DYNAMIC_CLASS(wxNotebook, wxControl)
187 IMPLEMENT_DYNAMIC_CLASS(wxNotebookPageInfo, wxObject )
188 #endif
189 IMPLEMENT_DYNAMIC_CLASS(wxNotebookEvent, wxNotifyEvent)
190
191 // ---------------------------------------------------------------------------
192 // private functions
193 // ---------------------------------------------------------------------------
194
195 #if USE_NOTEBOOK_ANTIFLICKER
196 // wnd proc for the spin button
197 LRESULT APIENTRY _EXPORT wxNotebookSpinBtnWndProc(HWND hWnd,
198 UINT message,
199 WPARAM wParam,
200 LPARAM lParam);
201
202 // ---------------------------------------------------------------------------
203 // global vars
204 // ---------------------------------------------------------------------------
205
206 // the pointer to standard spin button wnd proc
207 static WXFARPROC s_wndprocNotebookSpinBtn = (WXFARPROC)NULL;
208 #endif
209
210 // ============================================================================
211 // implementation
212 // ============================================================================
213
214 // ----------------------------------------------------------------------------
215 // wxNotebook construction
216 // ----------------------------------------------------------------------------
217
218 const wxNotebookPageInfoList& wxNotebook::GetPageInfos() const
219 {
220 wxNotebookPageInfoList* list = const_cast< wxNotebookPageInfoList* >( &m_pageInfos ) ;
221 WX_CLEAR_LIST( wxNotebookPageInfoList , *list ) ;
222 for( size_t i = 0 ; i < GetPageCount() ; ++i )
223 {
224 wxNotebookPageInfo *info = new wxNotebookPageInfo() ;
225 info->Create( const_cast<wxNotebook*>(this)->GetPage(i) , GetPageText(i) , GetSelection() == int(i) , GetPageImage(i) ) ;
226 list->Append( info ) ;
227 }
228 return m_pageInfos ;
229 }
230
231 // common part of all ctors
232 void wxNotebook::Init()
233 {
234 m_imageList = NULL;
235 m_nSelection = -1;
236
237 #if wxUSE_UXTHEME
238 m_hbrBackground = NULL;
239 #endif // wxUSE_UXTHEME
240 }
241
242 // default for dynamic class
243 wxNotebook::wxNotebook()
244 {
245 Init();
246 }
247
248 // the same arguments as for wxControl
249 wxNotebook::wxNotebook(wxWindow *parent,
250 wxWindowID id,
251 const wxPoint& pos,
252 const wxSize& size,
253 long style,
254 const wxString& name)
255 {
256 Init();
257
258 Create(parent, id, pos, size, style, name);
259 }
260
261 // Create() function
262 bool wxNotebook::Create(wxWindow *parent,
263 wxWindowID id,
264 const wxPoint& pos,
265 const wxSize& size,
266 long style,
267 const wxString& name)
268 {
269 #ifdef __WXWINCE__
270 // Not sure why, but without this style, there is no border
271 // around the notebook tabs.
272 if (style & wxNB_FLAT)
273 style |= wxBORDER_SUNKEN;
274 #endif
275
276 // comctl32.dll 6.0 doesn't support non-top tabs with visual styles (the
277 // control is simply not rendered correctly), so disable them in this case
278 const int verComCtl32 = wxApp::GetComCtl32Version();
279 if ( verComCtl32 == 600 )
280 {
281 // check if we use themes at all -- if we don't, we're still ok
282 #if wxUSE_UXTHEME
283 if ( wxUxThemeEngine::GetIfActive() )
284 #endif
285 {
286 style &= ~(wxNB_BOTTOM | wxNB_LEFT | wxNB_RIGHT);
287 }
288 }
289
290 LPCTSTR className = WC_TABCONTROL;
291
292 // SysTabCtl32 class has natively CS_HREDRAW and CS_VREDRAW enabled and it
293 // causes horrible flicker when resizing notebook, so get rid of it by
294 // using a class without these styles (but otherwise identical to it)
295 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
296 {
297 static ClassRegistrar s_clsNotebook;
298 if ( !s_clsNotebook.IsInitialized() )
299 {
300 // get a copy of standard class and modify it
301 WNDCLASS wc;
302
303 if ( ::GetClassInfo(::GetModuleHandle(NULL), WC_TABCONTROL, &wc) )
304 {
305 wc.lpszClassName = wxT("_wx_SysTabCtl32");
306 wc.style &= ~(CS_HREDRAW | CS_VREDRAW);
307
308 s_clsNotebook.Register(wc);
309 }
310 else
311 {
312 wxLogLastError(_T("GetClassInfoEx(SysTabCtl32)"));
313 }
314 }
315
316 // use our custom class if available but fall back to the standard
317 // notebook if we failed to register it
318 if ( s_clsNotebook.IsRegistered() )
319 {
320 // it's ok to use c_str() here as the static s_clsNotebook object
321 // has sufficiently long lifetime
322 className = s_clsNotebook.GetName().c_str();
323 }
324 }
325
326 if ( !CreateControl(parent, id, pos, size, style | wxTAB_TRAVERSAL,
327 wxDefaultValidator, name) )
328 return false;
329
330 if ( !MSWCreateControl(className, wxEmptyString, pos, size) )
331 return false;
332
333 #if wxUSE_UXTHEME
334 if ( HasFlag(wxNB_NOPAGETHEME) ||
335 wxSystemOptions::IsFalse(wxT("msw.notebook.themed-background")) )
336 {
337 SetBackgroundColour(GetThemeBackgroundColour());
338 }
339 #endif // wxUSE_UXTHEME
340
341 // Undocumented hack to get flat notebook style
342 // In fact, we should probably only do this in some
343 // curcumstances, i.e. if we know we will have a border
344 // at the bottom (the tab control doesn't draw it itself)
345 #if defined(__POCKETPC__) || defined(__SMARTPHONE__)
346 if (HasFlag(wxNB_FLAT))
347 {
348 SendMessage(m_hwnd, CCM_SETVERSION, COMCTL32_VERSION, 0);
349 if (!m_hasBgCol)
350 SetBackgroundColour(*wxWHITE);
351 }
352 #endif
353 return true;
354 }
355
356 WXDWORD wxNotebook::MSWGetStyle(long style, WXDWORD *exstyle) const
357 {
358 WXDWORD tabStyle = wxControl::MSWGetStyle(style, exstyle);
359
360 tabStyle |= WS_TABSTOP | TCS_TABS;
361
362 if ( style & wxNB_MULTILINE )
363 tabStyle |= TCS_MULTILINE;
364 if ( style & wxNB_FIXEDWIDTH )
365 tabStyle |= TCS_FIXEDWIDTH;
366
367 if ( style & wxNB_BOTTOM )
368 tabStyle |= TCS_RIGHT;
369 else if ( style & wxNB_LEFT )
370 tabStyle |= TCS_VERTICAL;
371 else if ( style & wxNB_RIGHT )
372 tabStyle |= TCS_VERTICAL | TCS_RIGHT;
373
374 // ex style
375 if ( exstyle )
376 {
377 // note that we never want to have the default WS_EX_CLIENTEDGE style
378 // as it looks too ugly for the notebooks
379 *exstyle = 0;
380 }
381
382 return tabStyle;
383 }
384
385 wxNotebook::~wxNotebook()
386 {
387 #if wxUSE_UXTHEME
388 if ( m_hbrBackground )
389 ::DeleteObject((HBRUSH)m_hbrBackground);
390 #endif // wxUSE_UXTHEME
391 }
392
393 // ----------------------------------------------------------------------------
394 // wxNotebook accessors
395 // ----------------------------------------------------------------------------
396
397 size_t wxNotebook::GetPageCount() const
398 {
399 // consistency check
400 wxASSERT( (int)m_pages.Count() == TabCtrl_GetItemCount(m_hwnd) );
401
402 return m_pages.Count();
403 }
404
405 int wxNotebook::GetRowCount() const
406 {
407 return TabCtrl_GetRowCount(m_hwnd);
408 }
409
410 int wxNotebook::SetSelection(size_t nPage)
411 {
412 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );
413
414 if ( int(nPage) != m_nSelection )
415 {
416 wxNotebookEvent event(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, m_windowId);
417 event.SetSelection(nPage);
418 event.SetOldSelection(m_nSelection);
419 event.SetEventObject(this);
420 if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
421 {
422 // program allows the page change
423 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
424 (void)GetEventHandler()->ProcessEvent(event);
425
426 TabCtrl_SetCurSel(m_hwnd, nPage);
427 }
428 }
429
430 return m_nSelection;
431 }
432
433 bool wxNotebook::SetPageText(size_t nPage, const wxString& strText)
434 {
435 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, wxT("notebook page out of range") );
436
437 TC_ITEM tcItem;
438 tcItem.mask = TCIF_TEXT;
439 tcItem.pszText = (wxChar *)strText.c_str();
440
441 return TabCtrl_SetItem(m_hwnd, nPage, &tcItem) != 0;
442 }
443
444 wxString wxNotebook::GetPageText(size_t nPage) const
445 {
446 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxEmptyString, wxT("notebook page out of range") );
447
448 wxChar buf[256];
449 TC_ITEM tcItem;
450 tcItem.mask = TCIF_TEXT;
451 tcItem.pszText = buf;
452 tcItem.cchTextMax = WXSIZEOF(buf);
453
454 wxString str;
455 if ( TabCtrl_GetItem(m_hwnd, nPage, &tcItem) )
456 str = tcItem.pszText;
457
458 return str;
459 }
460
461 int wxNotebook::GetPageImage(size_t nPage) const
462 {
463 wxCHECK_MSG( IS_VALID_PAGE(nPage), -1, wxT("notebook page out of range") );
464
465 TC_ITEM tcItem;
466 tcItem.mask = TCIF_IMAGE;
467
468 return TabCtrl_GetItem(m_hwnd, nPage, &tcItem) ? tcItem.iImage : -1;
469 }
470
471 bool wxNotebook::SetPageImage(size_t nPage, int nImage)
472 {
473 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, wxT("notebook page out of range") );
474
475 TC_ITEM tcItem;
476 tcItem.mask = TCIF_IMAGE;
477 tcItem.iImage = nImage;
478
479 return TabCtrl_SetItem(m_hwnd, nPage, &tcItem) != 0;
480 }
481
482 void wxNotebook::SetImageList(wxImageList* imageList)
483 {
484 wxNotebookBase::SetImageList(imageList);
485
486 if ( imageList )
487 {
488 TabCtrl_SetImageList(m_hwnd, (HIMAGELIST)imageList->GetHIMAGELIST());
489 }
490 }
491
492 // ----------------------------------------------------------------------------
493 // wxNotebook size settings
494 // ----------------------------------------------------------------------------
495
496 wxRect wxNotebook::GetPageSize() const
497 {
498 wxRect r;
499
500 RECT rc;
501 ::GetClientRect(GetHwnd(), &rc);
502
503 // This check is to work around a bug in TabCtrl_AdjustRect which will
504 // cause a crash on win2k, or on XP with themes disabled, if the
505 // wxNB_MULTILINE style is used and the rectangle is very small, (such as
506 // when the notebook is first created.) The value of 20 is just
507 // arbitrarily chosen, if there is a better way to determine this value
508 // then please do so. --RD
509 if ( !HasFlag(wxNB_MULTILINE) || (rc.right > 20 && rc.bottom > 20) )
510 {
511 TabCtrl_AdjustRect(m_hwnd, false, &rc);
512
513 wxCopyRECTToRect(rc, r);
514 }
515
516 return r;
517 }
518
519 void wxNotebook::SetPageSize(const wxSize& size)
520 {
521 // transform the page size into the notebook size
522 RECT rc;
523 rc.left =
524 rc.top = 0;
525 rc.right = size.x;
526 rc.bottom = size.y;
527
528 TabCtrl_AdjustRect(GetHwnd(), true, &rc);
529
530 // and now set it
531 SetSize(rc.right - rc.left, rc.bottom - rc.top);
532 }
533
534 void wxNotebook::SetPadding(const wxSize& padding)
535 {
536 TabCtrl_SetPadding(GetHwnd(), padding.x, padding.y);
537 }
538
539 // Windows-only at present. Also, you must use the wxNB_FIXEDWIDTH
540 // style.
541 void wxNotebook::SetTabSize(const wxSize& sz)
542 {
543 ::SendMessage(GetHwnd(), TCM_SETITEMSIZE, 0, MAKELPARAM(sz.x, sz.y));
544 }
545
546 wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage) const
547 {
548 wxSize sizeTotal = sizePage;
549
550 // We need to make getting tab size part of the wxWidgets API.
551 wxSize tabSize;
552 if (GetPageCount() > 0)
553 {
554 RECT rect;
555 TabCtrl_GetItemRect((HWND) GetHWND(), 0, & rect);
556 tabSize.x = rect.right - rect.left;
557 tabSize.y = rect.bottom - rect.top;
558 }
559 if ( HasFlag(wxNB_LEFT) || HasFlag(wxNB_RIGHT) )
560 {
561 sizeTotal.x += tabSize.x + 7;
562 sizeTotal.y += 7;
563 }
564 else
565 {
566 sizeTotal.x += 7;
567 sizeTotal.y += tabSize.y + 7;
568 }
569
570 return sizeTotal;
571 }
572
573 void wxNotebook::AdjustPageSize(wxNotebookPage *page)
574 {
575 wxCHECK_RET( page, _T("NULL page in wxNotebook::AdjustPageSize") );
576
577 const wxRect r = GetPageSize();
578 if ( !r.IsEmpty() )
579 {
580 page->SetSize(r);
581 }
582 }
583
584 // ----------------------------------------------------------------------------
585 // wxNotebook operations
586 // ----------------------------------------------------------------------------
587
588 // remove one page from the notebook, without deleting
589 wxNotebookPage *wxNotebook::DoRemovePage(size_t nPage)
590 {
591 wxNotebookPage *pageRemoved = wxNotebookBase::DoRemovePage(nPage);
592 if ( !pageRemoved )
593 return NULL;
594
595 TabCtrl_DeleteItem(m_hwnd, nPage);
596
597 if ( m_pages.IsEmpty() )
598 {
599 // no selection any more, the notebook becamse empty
600 m_nSelection = -1;
601 }
602 else // notebook still not empty
603 {
604 int selNew = TabCtrl_GetCurSel(m_hwnd);
605 if (selNew != -1)
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_nSelection = selNew;
612 m_pages[m_nSelection]->Refresh();
613 }
614 else if (int(nPage) == m_nSelection)
615 {
616 // The selection was deleted.
617
618 // Determine new selection.
619 if (m_nSelection == int(GetPageCount()))
620 selNew = m_nSelection - 1;
621 else
622 selNew = m_nSelection;
623
624 // m_nSelection must be always valid so reset it before calling
625 // SetSelection()
626 m_nSelection = -1;
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(m_hwnd);
649
650 m_nSelection = -1;
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, _T("NULL page in wxNotebook::InsertPage") );
664 wxCHECK_MSG( IS_VALID_PAGE(nPage) || nPage == GetPageCount(), false,
665 _T("invalid index in wxNotebook::InsertPage") );
666
667 wxASSERT_MSG( pPage->GetParent() == this,
668 _T("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 = (wxChar *)strText.c_str(); // const_cast
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(m_hwnd, nPage, &tcItem) == -1 )
708 {
709 wxLogError(wxT("Can't create the notebook page '%s'."), strText.c_str());
710
711 return false;
712 }
713
714 // succeeded: save the pointer to the page
715 m_pages.Insert(pPage, nPage);
716
717 // we may need to adjust the size again if the notebook size changed:
718 // normally this only happens for the first page we add (the tabs which
719 // hadn't been there before are now shown) but for a multiline notebook it
720 // can happen for any page at all as a new row could have been started
721 if ( m_pages.GetCount() == 1 || HasFlag(wxNB_MULTILINE) )
722 {
723 AdjustPageSize(pPage);
724 }
725
726 // now deal with the selection
727 // ---------------------------
728
729 // if the inserted page is before the selected one, we must update the
730 // index of the selected page
731 if ( int(nPage) <= m_nSelection )
732 {
733 // one extra page added
734 m_nSelection++;
735 }
736
737 // some page should be selected: either this one or the first one if there
738 // is still no selection
739 int selNew = -1;
740 if ( bSelect )
741 selNew = nPage;
742 else if ( m_nSelection == -1 )
743 selNew = 0;
744
745 if ( selNew != -1 )
746 SetSelection(selNew);
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 |= wxNB_HITTEST_NOWHERE;
766 if ((hitTestInfo.flags & TCHT_ONITEM) == TCHT_ONITEM)
767 *flags |= wxNB_HITTEST_ONITEM;
768 if ((hitTestInfo.flags & TCHT_ONITEMICON) == TCHT_ONITEMICON)
769 *flags |= wxNB_HITTEST_ONICON;
770 if ((hitTestInfo.flags & TCHT_ONITEMLABEL) == TCHT_ONITEMLABEL)
771 *flags |= wxNB_HITTEST_ONLABEL;
772 }
773
774 return item;
775 }
776
777
778 // ----------------------------------------------------------------------------
779 // wxNotebook callbacks
780 // ----------------------------------------------------------------------------
781
782 void wxNotebook::OnSize(wxSizeEvent& event)
783 {
784 if ( GetPageCount() == 0 )
785 {
786 // Prevents droppings on resize, but does cause some flicker
787 // when there are no pages.
788 Refresh();
789 event.Skip();
790 return;
791 }
792 #ifndef __WXWINCE__
793 else
794 {
795 // Without this, we can sometimes get droppings at the edges
796 // of a notebook, for example a notebook in a splitter window.
797 // This needs to be reconciled with the RefreshRect calls
798 // at the end of this function, which weren't enough to prevent
799 // the droppings.
800
801 wxSize sz = GetClientSize();
802
803 // Refresh right side
804 wxRect rect(sz.x-4, 0, 4, sz.y);
805 RefreshRect(rect);
806
807 // Refresh bottom side
808 rect = wxRect(0, sz.y-4, sz.x, 4);
809 RefreshRect(rect);
810
811 // Refresh left side
812 rect = wxRect(0, 0, 4, sz.y);
813 RefreshRect(rect);
814 }
815 #endif
816
817 // fit all the notebook pages to the tab control's display area
818
819 RECT rc;
820 rc.left = rc.top = 0;
821 GetSize((int *)&rc.right, (int *)&rc.bottom);
822
823 // save the total size, we'll use it below
824 int widthNbook = rc.right - rc.left,
825 heightNbook = rc.bottom - rc.top;
826
827 // there seems to be a bug in the implementation of TabCtrl_AdjustRect(): it
828 // returns completely false values for multiline tab controls after the tabs
829 // are added but before getting the first WM_SIZE (off by ~50 pixels, see
830 //
831 // http://sf.net/tracker/index.php?func=detail&aid=645323&group_id=9863&atid=109863
832 //
833 // and the only work around I could find was this ugly hack... without it
834 // simply toggling the "multiline" checkbox in the notebook sample resulted
835 // in a noticeable page displacement
836 if ( HasFlag(wxNB_MULTILINE) )
837 {
838 // avoid an infinite recursion: we get another notification too!
839 static bool s_isInOnSize = false;
840
841 if ( !s_isInOnSize )
842 {
843 s_isInOnSize = true;
844 SendMessage(GetHwnd(), WM_SIZE, SIZE_RESTORED,
845 MAKELPARAM(rc.right, rc.bottom));
846 s_isInOnSize = false;
847 }
848 }
849
850 #if wxUSE_UXTHEME
851 // background bitmap size has changed, update the brush using it too
852 UpdateBgBrush();
853 #endif // wxUSE_UXTHEME
854
855 TabCtrl_AdjustRect(m_hwnd, false, &rc);
856
857 int width = rc.right - rc.left,
858 height = rc.bottom - rc.top;
859 size_t nCount = m_pages.Count();
860 for ( size_t nPage = 0; nPage < nCount; nPage++ ) {
861 wxNotebookPage *pPage = m_pages[nPage];
862 pPage->SetSize(rc.left, rc.top, width, height);
863 }
864
865
866 // unless we had already repainted everything, we now need to refresh
867 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
868 {
869 // invalidate areas not covered by pages
870 RefreshRect(wxRect(0, 0, widthNbook, rc.top), false);
871 RefreshRect(wxRect(0, rc.top, rc.left, height), false);
872 RefreshRect(wxRect(0, rc.bottom, widthNbook, heightNbook - rc.bottom),
873 false);
874 RefreshRect(wxRect(rc.right, rc.top, widthNbook - rc.bottom, height),
875 false);
876 }
877
878 event.Skip();
879 }
880
881 void wxNotebook::OnSelChange(wxNotebookEvent& event)
882 {
883 // is it our tab control?
884 if ( event.GetEventObject() == this )
885 {
886 int sel = event.GetOldSelection();
887 if ( sel != -1 )
888 m_pages[sel]->Show(false);
889
890 sel = event.GetSelection();
891 if ( sel != -1 )
892 {
893 wxNotebookPage *pPage = m_pages[sel];
894 pPage->Show(true);
895 pPage->SetFocus();
896
897 // If the newly focused window is not a child of the new page,
898 // SetFocus was not successful and the notebook itself should be
899 // focused
900 wxWindow *currentFocus = FindFocus();
901 wxWindow *startFocus = currentFocus;
902 while ( currentFocus && currentFocus != pPage && currentFocus != this )
903 currentFocus = currentFocus->GetParent();
904
905 if ( startFocus == pPage || currentFocus != pPage )
906 SetFocus();
907
908 }
909 else // no pages in the notebook, give the focus to itself
910 {
911 SetFocus();
912 }
913
914 m_nSelection = sel;
915 }
916
917 // we want to give others a chance to process this message as well
918 event.Skip();
919 }
920
921 bool wxNotebook::MSWTranslateMessage(WXMSG *wxmsg)
922 {
923 const MSG * const msg = (MSG *)wxmsg;
924
925 // intercept TAB, CTRL+TAB and CTRL+SHIFT+TAB for processing by wxNotebook.
926 // TAB will be passed to the currently selected page, CTRL+TAB and
927 // CTRL+SHIFT+TAB will be processed by the notebook itself. do not
928 // intercept SHIFT+TAB. This goes to the parent of the notebook which will
929 // process it.
930 if ( msg->message == WM_KEYDOWN && msg->wParam == VK_TAB &&
931 msg->hwnd == m_hwnd &&
932 (wxIsCtrlDown() || !wxIsShiftDown()) )
933 {
934 return MSWProcessMessage(wxmsg);
935 }
936
937 return false;
938 }
939
940 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
941 {
942 if ( event.IsWindowChange() ) {
943 // change pages
944 AdvanceSelection(event.GetDirection());
945 }
946 else {
947 // we get this event in 3 cases
948 //
949 // a) one of our pages might have generated it because the user TABbed
950 // out from it in which case we should propagate the event upwards and
951 // our parent will take care of setting the focus to prev/next sibling
952 //
953 // or
954 //
955 // b) the parent panel wants to give the focus to us so that we
956 // forward it to our selected page. We can't deal with this in
957 // OnSetFocus() because we don't know which direction the focus came
958 // from in this case and so can't choose between setting the focus to
959 // first or last panel child
960 //
961 // or
962 //
963 // c) we ourselves (see MSWTranslateMessage) generated the event
964 //
965 wxWindow * const parent = GetParent();
966
967 // the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
968 const bool isFromParent = event.GetEventObject() == (wxObject*) parent;
969 const bool isFromSelf = event.GetEventObject() == (wxObject*) this;
970
971 if ( isFromParent || isFromSelf )
972 {
973 // no, it doesn't come from child, case (b) or (c): forward to a
974 // page but only if direction is backwards (TAB) or from ourselves,
975 if ( m_nSelection != -1 &&
976 (!event.GetDirection() || isFromSelf) )
977 {
978 // so that the page knows that the event comes from it's parent
979 // and is being propagated downwards
980 event.SetEventObject(this);
981
982 wxWindow *page = m_pages[m_nSelection];
983 if ( !page->GetEventHandler()->ProcessEvent(event) )
984 {
985 page->SetFocus();
986 }
987 //else: page manages focus inside it itself
988 }
989 else // otherwise set the focus to the notebook itself
990 {
991 SetFocus();
992 }
993 }
994 else
995 {
996 // it comes from our child, case (a), pass to the parent, but only
997 // if the direction is forwards. Otherwise set the focus to the
998 // notebook itself. The notebook is always the 'first' control of a
999 // page.
1000 if ( !event.GetDirection() )
1001 {
1002 SetFocus();
1003 }
1004 else if ( parent )
1005 {
1006 event.SetCurrentFocus(this);
1007 parent->GetEventHandler()->ProcessEvent(event);
1008 }
1009 }
1010 }
1011 }
1012
1013 #if wxUSE_UXTHEME
1014
1015 bool wxNotebook::DoDrawBackground(WXHDC hDC, wxWindow *child)
1016 {
1017 wxUxThemeHandle theme(child ? child : this, L"TAB");
1018 if ( !theme )
1019 return false;
1020
1021 // get the notebook client rect (we're not interested in drawing tabs
1022 // themselves)
1023 wxRect r = GetPageSize();
1024 if ( r.IsEmpty() )
1025 return false;
1026
1027 RECT rc;
1028 wxCopyRectToRECT(r, rc);
1029
1030 // map rect to the coords of the window we're drawing in
1031 if ( child )
1032 ::MapWindowPoints(GetHwnd(), GetHwndOf(child), (POINT *)&rc, 2);
1033
1034
1035 // apparently DrawThemeBackground() modifies the rect passed to it and if we
1036 // don't do these adjustments, there are some drawing artifacts which are
1037 // only visible with some non default themes; so modify the rect here using
1038 // the magic numbers below so that it still paints the correct area
1039 rc.left -= 2;
1040 rc.top -= 2;
1041 rc.right += 4;
1042 rc.bottom += 5;
1043
1044
1045 wxUxThemeEngine::Get()->DrawThemeBackground
1046 (
1047 theme,
1048 hDC,
1049 9 /* TABP_PANE */,
1050 0,
1051 &rc,
1052 NULL
1053 );
1054
1055 return true;
1056 }
1057
1058 WXHBRUSH wxNotebook::QueryBgBitmap()
1059 {
1060 wxRect r = GetPageSize();
1061 if ( r.IsEmpty() )
1062 return 0;
1063
1064 WindowHDC hDC(GetHwnd());
1065 MemoryHDC hDCMem(hDC);
1066 CompatibleBitmap hBmp(hDC, r.x + r.width, r.y + r.height);
1067
1068 SelectInHDC selectBmp(hDCMem, hBmp);
1069
1070 if ( !DoDrawBackground((WXHDC)(HDC)hDCMem) )
1071 return 0;
1072
1073 return (WXHBRUSH)::CreatePatternBrush(hBmp);
1074 }
1075
1076 void wxNotebook::UpdateBgBrush()
1077 {
1078 if ( m_hbrBackground )
1079 ::DeleteObject((HBRUSH)m_hbrBackground);
1080
1081 if ( !m_hasBgCol && wxUxThemeEngine::GetIfActive() )
1082 {
1083 m_hbrBackground = QueryBgBitmap();
1084 }
1085 else // no themes or we've got user-defined solid colour
1086 {
1087 m_hbrBackground = NULL;
1088 }
1089 }
1090
1091 WXHBRUSH wxNotebook::MSWGetBgBrushForChild(WXHDC hDC, WXHWND hWnd)
1092 {
1093 if ( m_hbrBackground )
1094 {
1095 // before drawing with the background brush, we need to position it
1096 // correctly
1097 RECT rc;
1098 ::GetWindowRect((HWND)hWnd, &rc);
1099
1100 ::MapWindowPoints(NULL, GetHwnd(), (POINT *)&rc, 1);
1101
1102 if ( !::SetBrushOrgEx((HDC)hDC, -rc.left, -rc.top, NULL) )
1103 {
1104 wxLogLastError(_T("SetBrushOrgEx(notebook bg brush)"));
1105 }
1106
1107 return m_hbrBackground;
1108 }
1109
1110 return wxNotebookBase::MSWGetBgBrushForChild(hDC, hWnd);
1111 }
1112
1113 bool wxNotebook::MSWPrintChild(WXHDC hDC, wxWindow *child)
1114 {
1115 // solid background colour overrides themed background drawing
1116 if ( !UseBgCol() && DoDrawBackground(hDC, child) )
1117 return true;
1118
1119 // If we're using a solid colour (for example if we've switched off
1120 // theming for this notebook), paint it
1121 if (UseBgCol())
1122 {
1123 wxRect r = GetPageSize();
1124 if ( r.IsEmpty() )
1125 return false;
1126
1127 RECT rc;
1128 wxCopyRectToRECT(r, rc);
1129
1130 // map rect to the coords of the window we're drawing in
1131 if ( child )
1132 ::MapWindowPoints(GetHwnd(), GetHwndOf(child), (POINT *)&rc, 2);
1133
1134 wxBrush brush(GetBackgroundColour());
1135 HBRUSH hbr = GetHbrushOf(brush);
1136
1137 ::FillRect((HDC) hDC, &rc, hbr);
1138
1139 return true;
1140 }
1141
1142 return wxNotebookBase::MSWPrintChild(hDC, child);
1143 }
1144
1145 #endif // wxUSE_UXTHEME
1146
1147 // Windows only: attempts to get colour for UX theme page background
1148 wxColour wxNotebook::GetThemeBackgroundColour() const
1149 {
1150 #if wxUSE_UXTHEME
1151 if (wxUxThemeEngine::Get())
1152 {
1153 wxUxThemeHandle hTheme((wxNotebook*) this, L"TAB");
1154 if (hTheme)
1155 {
1156 // This is total guesswork.
1157 // See PlatformSDK\Include\Tmschema.h for values
1158 COLORREF themeColor;
1159 wxUxThemeEngine::Get()->GetThemeColor(
1160 hTheme,
1161 10 /* TABP_BODY */,
1162 1 /* NORMAL */,
1163 3821 /* FILLCOLORHINT */,
1164 &themeColor);
1165
1166 /*
1167 [DS] Workaround for WindowBlinds:
1168 Some themes return a near black theme color using FILLCOLORHINT,
1169 this makes notebook pages have an ugly black background and makes
1170 text (usually black) unreadable. Retry again with FILLCOLOR.
1171
1172 This workaround potentially breaks appearance of some themes,
1173 but in practice it already fixes some themes.
1174 */
1175 if (themeColor == 1)
1176 {
1177 wxUxThemeEngine::Get()->GetThemeColor(
1178 hTheme,
1179 10 /* TABP_BODY */,
1180 1 /* NORMAL */,
1181 3802 /* FILLCOLOR */,
1182 &themeColor);
1183 }
1184
1185 return wxRGBToColour(themeColor);
1186 }
1187 }
1188 #endif // wxUSE_UXTHEME
1189
1190 return GetBackgroundColour();
1191 }
1192
1193 // ----------------------------------------------------------------------------
1194 // wxNotebook base class virtuals
1195 // ----------------------------------------------------------------------------
1196
1197 #if wxUSE_CONSTRAINTS
1198
1199 // override these 2 functions to do nothing: everything is done in OnSize
1200
1201 void wxNotebook::SetConstraintSizes(bool WXUNUSED(recurse))
1202 {
1203 // don't set the sizes of the pages - their correct size is not yet known
1204 wxControl::SetConstraintSizes(false);
1205 }
1206
1207 bool wxNotebook::DoPhase(int WXUNUSED(nPhase))
1208 {
1209 return true;
1210 }
1211
1212 #endif // wxUSE_CONSTRAINTS
1213
1214 // ----------------------------------------------------------------------------
1215 // wxNotebook Windows message handlers
1216 // ----------------------------------------------------------------------------
1217
1218 bool wxNotebook::MSWOnScroll(int orientation, WXWORD nSBCode,
1219 WXWORD pos, WXHWND control)
1220 {
1221 // don't generate EVT_SCROLLWIN events for the WM_SCROLLs coming from the
1222 // up-down control
1223 if ( control )
1224 return false;
1225
1226 return wxNotebookBase::MSWOnScroll(orientation, nSBCode, pos, control);
1227 }
1228
1229 bool wxNotebook::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result)
1230 {
1231 wxNotebookEvent event(wxEVT_NULL, m_windowId);
1232
1233 NMHDR* hdr = (NMHDR *)lParam;
1234 switch ( hdr->code ) {
1235 case TCN_SELCHANGE:
1236 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
1237 break;
1238
1239 case TCN_SELCHANGING:
1240 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING);
1241 break;
1242
1243 default:
1244 return wxControl::MSWOnNotify(idCtrl, lParam, result);
1245 }
1246
1247 event.SetSelection(TabCtrl_GetCurSel(m_hwnd));
1248 event.SetOldSelection(m_nSelection);
1249 event.SetEventObject(this);
1250 event.SetInt(idCtrl);
1251
1252 bool processed = GetEventHandler()->ProcessEvent(event);
1253 *result = !event.IsAllowed();
1254 return processed;
1255 }
1256
1257 #ifndef __WXWINCE__
1258 void wxNotebook::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
1259 {
1260 // do nothing here
1261 }
1262
1263 void wxNotebook::OnPaint(wxPaintEvent& WXUNUSED(event))
1264 {
1265 // Better painting behaviour, at the expense of system resources
1266 #if USE_NOTEBOOK_ANTIFLICKER
1267 wxPaintDC dc(this);
1268 wxMemoryDC memdc;
1269 RECT rc;
1270 ::GetClientRect(GetHwnd(), &rc);
1271 wxBitmap bmp(rc.right, rc.bottom);
1272 memdc.SelectObject(bmp);
1273
1274 // iterate over all child windows to find spin button
1275 for ( HWND child = ::GetWindow(GetHwnd(), GW_CHILD);
1276 child;
1277 child = ::GetWindow(child, GW_HWNDNEXT) )
1278 {
1279 wxWindow *childWindow = wxFindWinFromHandle((WXHWND)child);
1280
1281 // see if it exists, if no wxWindow found then assume it's the spin btn
1282 if ( !childWindow )
1283 {
1284 // subclass the spin button to override WM_ERASEBKGND
1285 if ( !s_wndprocNotebookSpinBtn )
1286 s_wndprocNotebookSpinBtn = (WXFARPROC)wxGetWindowProc(child);
1287
1288 wxSetWindowProc(child, wxNotebookSpinBtnWndProc);
1289 break;
1290 }
1291 }
1292
1293 HBRUSH hbr = (HBRUSH)m_hbrBackground;
1294
1295 // if there is no special brush just use the solid background colour
1296 wxBrush brush;
1297 if ( !hbr )
1298 {
1299 brush = wxBrush(GetBackgroundColour());
1300 hbr = GetHbrushOf(brush);
1301 }
1302
1303 ::FillRect(GetHdcOf(memdc), &rc, hbr);
1304
1305 MSWDefWindowProc(WM_PAINT, (WPARAM)memdc.GetHDC(), 0);
1306
1307 dc.Blit(0, 0, rc.right, rc.bottom, &memdc, 0, 0);
1308 #endif
1309 }
1310 #endif
1311 // __WXWINCE__
1312
1313 #if USE_NOTEBOOK_ANTIFLICKER
1314 // ---------------------------------------------------------------------------
1315 // window proc for spin button
1316 // ---------------------------------------------------------------------------
1317
1318 LRESULT APIENTRY _EXPORT wxNotebookSpinBtnWndProc(HWND hwnd,
1319 UINT message,
1320 WPARAM wParam,
1321 LPARAM lParam)
1322 {
1323 if ( message == WM_ERASEBKGND )
1324 return 0;
1325
1326 return ::CallWindowProc(CASTWNDPROC s_wndprocNotebookSpinBtn, hwnd, message, wParam, lParam);
1327 }
1328
1329 #endif
1330 // USE_NOTEBOOK_ANTIFLICKER
1331
1332 #endif // wxUSE_NOTEBOOK