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