Remove all lines containing cvs/svn "$Id$" keyword.
[wxWidgets.git] / src / generic / notebook.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/notebook.cpp
3 // Purpose: generic implementation of wxNotebook
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 17/09/98
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_NOTEBOOK
27
28 #include "wx/notebook.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/string.h"
32 #include "wx/log.h"
33 #include "wx/dcclient.h"
34 #include "wx/settings.h"
35 #endif
36
37 #include "wx/imaglist.h"
38 #include "wx/generic/tabg.h"
39
40 // ----------------------------------------------------------------------------
41 // macros
42 // ----------------------------------------------------------------------------
43
44 // check that the page index is valid
45 #define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())
46
47 // ----------------------------------------------------------------------------
48 // event table
49 // ----------------------------------------------------------------------------
50
51 BEGIN_EVENT_TABLE(wxNotebook, wxBookCtrlBase)
52 EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, wxNotebook::OnSelChange)
53 EVT_SIZE(wxNotebook::OnSize)
54 EVT_PAINT(wxNotebook::OnPaint)
55 EVT_MOUSE_EVENTS(wxNotebook::OnMouseEvent)
56 EVT_SET_FOCUS(wxNotebook::OnSetFocus)
57 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
58 END_EVENT_TABLE()
59
60 // ============================================================================
61 // implementation
62 // ============================================================================
63
64 // ============================================================================
65 // Private class
66 // ============================================================================
67
68 WX_DECLARE_HASH_MAP(int, wxNotebookPage*, wxIntegerHash, wxIntegerEqual,
69 wxIntToNotebookPageHashMap);
70
71 WX_DECLARE_HASH_MAP(wxNotebookPage*, int, wxPointerHash, wxPointerEqual,
72 wxNotebookPageToIntHashMap);
73
74 // This reuses wxTabView to draw the tabs.
75 class WXDLLEXPORT wxNotebookTabView: public wxTabView
76 {
77 DECLARE_DYNAMIC_CLASS(wxNotebookTabView)
78 public:
79 wxNotebookTabView(wxNotebook* notebook, long style = wxTAB_STYLE_DRAW_BOX | wxTAB_STYLE_COLOUR_INTERIOR);
80 virtual ~wxNotebookTabView(void);
81
82 // Called when a tab is activated
83 virtual void OnTabActivate(int activateId, int deactivateId);
84 // Allows vetoing
85 virtual bool OnTabPreActivate(int activateId, int deactivateId);
86
87 // map integer ids used by wxTabView to wxNotebookPage pointers
88 int GetId(wxNotebookPage *page);
89 wxNotebookPage *GetPage(int id) { return m_idToPage[id]; }
90
91 protected:
92 wxNotebook* m_notebook;
93
94 private:
95 wxIntToNotebookPageHashMap m_idToPage;
96 wxNotebookPageToIntHashMap m_pageToId;
97 int m_nextid;
98 };
99
100 static int GetPageId(wxTabView *tabview, wxNotebookPage *page)
101 {
102 return static_cast<wxNotebookTabView*>(tabview)->GetId(page);
103 }
104
105 // ----------------------------------------------------------------------------
106 // wxNotebook construction
107 // ----------------------------------------------------------------------------
108
109 // common part of all ctors
110 void wxNotebook::Init()
111 {
112 m_tabView = NULL;
113 m_selection = -1;
114 }
115
116 // default for dynamic class
117 wxNotebook::wxNotebook()
118 {
119 Init();
120 }
121
122 // the same arguments as for wxControl
123 wxNotebook::wxNotebook(wxWindow *parent,
124 wxWindowID id,
125 const wxPoint& pos,
126 const wxSize& size,
127 long style,
128 const wxString& name)
129 {
130 Init();
131
132 Create(parent, id, pos, size, style, name);
133 }
134
135 // Create() function
136 bool wxNotebook::Create(wxWindow *parent,
137 wxWindowID id,
138 const wxPoint& pos,
139 const wxSize& size,
140 long style,
141 const wxString& name)
142 {
143 // base init
144 SetName(name);
145
146 if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
147 style |= wxBK_TOP;
148
149 m_windowId = id == wxID_ANY ? NewControlId() : id;
150
151 if (!wxControl::Create(parent, id, pos, size, style|wxNO_BORDER, wxDefaultValidator, name))
152 return false;
153
154 SetTabView(new wxNotebookTabView(this));
155
156 return true;
157 }
158
159 // dtor
160 wxNotebook::~wxNotebook()
161 {
162 delete m_tabView;
163 }
164
165 // ----------------------------------------------------------------------------
166 // wxNotebook accessors
167 // ----------------------------------------------------------------------------
168 int wxNotebook::GetRowCount() const
169 {
170 // TODO
171 return 0;
172 }
173
174 int wxNotebook::SetSelection(size_t nPage)
175 {
176 wxASSERT( IS_VALID_PAGE(nPage) );
177
178 wxNotebookPage* pPage = GetPage(nPage);
179
180 m_tabView->SetTabSelection(GetPageId(m_tabView, pPage));
181
182 // TODO
183 return 0;
184 }
185
186 int wxNotebook::ChangeSelection(size_t nPage)
187 {
188 // FIXME: currently it does generate events too
189 return SetSelection(nPage);
190 }
191
192 #if 0
193 void wxNotebook::AdvanceSelection(bool bForward)
194 {
195 int nSel = GetSelection();
196 int nMax = GetPageCount() - 1;
197 if ( bForward )
198 SetSelection(nSel == nMax ? 0 : nSel + 1);
199 else
200 SetSelection(nSel == 0 ? nMax : nSel - 1);
201 }
202 #endif
203
204 bool wxNotebook::SetPageText(size_t nPage, const wxString& strText)
205 {
206 wxASSERT( IS_VALID_PAGE(nPage) );
207
208 wxNotebookPage* page = GetPage(nPage);
209 if (page)
210 {
211 m_tabView->SetTabText(GetPageId(m_tabView, page), strText);
212 Refresh();
213 return true;
214 }
215
216 return false;
217 }
218
219 wxString wxNotebook::GetPageText(size_t nPage) const
220 {
221 wxASSERT( IS_VALID_PAGE(nPage) );
222
223 wxNotebookPage* page = ((wxNotebook*)this)->GetPage(nPage);
224 if (page)
225 return m_tabView->GetTabText(GetPageId(m_tabView, page));
226 else
227 return wxEmptyString;
228 }
229
230 int wxNotebook::GetPageImage(size_t WXUNUSED_UNLESS_DEBUG(nPage)) const
231 {
232 wxASSERT( IS_VALID_PAGE(nPage) );
233
234 // TODO
235 return 0;
236 }
237
238 bool wxNotebook::SetPageImage(size_t WXUNUSED_UNLESS_DEBUG(nPage),
239 int WXUNUSED(nImage))
240 {
241 wxASSERT( IS_VALID_PAGE(nPage) );
242
243 // TODO
244 return false;
245 }
246
247 // set the size (the same for all pages)
248 void wxNotebook::SetPageSize(const wxSize& WXUNUSED(size))
249 {
250 // TODO
251 }
252
253 // set the padding between tabs (in pixels)
254 void wxNotebook::SetPadding(const wxSize& WXUNUSED(padding))
255 {
256 // TODO
257 }
258
259 // set the size of the tabs for wxNB_FIXEDWIDTH controls
260 void wxNotebook::SetTabSize(const wxSize& WXUNUSED(sz))
261 {
262 // TODO
263 }
264
265 // ----------------------------------------------------------------------------
266 // wxNotebook operations
267 // ----------------------------------------------------------------------------
268
269 // remove one page from the notebook and delete it
270 bool wxNotebook::DeletePage(size_t nPage)
271 {
272 wxCHECK( IS_VALID_PAGE(nPage), false );
273
274 if (m_selection != -1)
275 {
276 m_pages[m_selection]->Show(false);
277 m_pages[m_selection]->Lower();
278 }
279
280 wxNotebookPage* pPage = GetPage(nPage);
281
282 m_tabView->RemoveTab(GetPageId(m_tabView, pPage));
283
284 m_pages.Remove(pPage);
285 delete pPage;
286
287 if (m_pages.GetCount() == 0)
288 {
289 m_selection = -1;
290 m_tabView->SetTabSelection(-1, false);
291 }
292 else if (m_selection > -1)
293 {
294 m_selection = -1;
295
296 m_tabView->SetTabSelection(GetPageId(m_tabView, GetPage(0)), false);
297
298 if (m_selection != 0)
299 ChangePage(-1, 0);
300 }
301
302 RefreshLayout(false);
303
304 return true;
305 }
306
307 bool wxNotebook::DeletePage(wxNotebookPage* page)
308 {
309 int pagePos = FindPagePosition(page);
310 if (pagePos > -1)
311 return DeletePage(pagePos);
312 else
313 return false;
314 }
315
316 bool wxNotebook::RemovePage(size_t nPage)
317 {
318 return DoRemovePage(nPage) != NULL;
319 }
320
321 // remove one page from the notebook
322 wxWindow* wxNotebook::DoRemovePage(size_t nPage)
323 {
324 wxCHECK( IS_VALID_PAGE(nPage), NULL );
325
326 m_pages[nPage]->Show(false);
327 // m_pages[nPage]->Lower();
328
329 wxNotebookPage* pPage = GetPage(nPage);
330
331 m_tabView->RemoveTab(GetPageId(m_tabView, pPage));
332
333 m_pages.Remove(pPage);
334
335 if (m_pages.GetCount() == 0)
336 {
337 m_selection = -1;
338 m_tabView->SetTabSelection(-1, true);
339 }
340 else if (m_selection > -1)
341 {
342 // Only change the selection if the page we
343 // deleted was the selection.
344 if (nPage == (size_t)m_selection)
345 {
346 m_selection = -1;
347 // Select the first tab. Generates a ChangePage.
348 m_tabView->SetTabSelection(0, true);
349 }
350 else
351 {
352 // We must adjust which tab we think is selected.
353 // If greater than the page we deleted, it must be moved down
354 // a notch.
355 if (size_t(m_selection) > nPage)
356 m_selection -- ;
357 }
358 }
359
360 RefreshLayout(false);
361
362 return pPage;
363 }
364
365 bool wxNotebook::RemovePage(wxNotebookPage* page)
366 {
367 int pagePos = FindPagePosition(page);
368 if (pagePos > -1)
369 return RemovePage(pagePos);
370 else
371 return false;
372 }
373
374 // Find the position of the wxNotebookPage, -1 if not found.
375 int wxNotebook::FindPagePosition(wxNotebookPage* page) const
376 {
377 size_t nPageCount = GetPageCount();
378 size_t nPage;
379 for ( nPage = 0; nPage < nPageCount; nPage++ )
380 if (m_pages[nPage] == page)
381 return nPage;
382 return -1;
383 }
384
385 // remove all pages
386 bool wxNotebook::DeleteAllPages()
387 {
388 m_tabView->ClearTabs(true);
389
390 size_t nPageCount = GetPageCount();
391 size_t nPage;
392 for ( nPage = 0; nPage < nPageCount; nPage++ )
393 delete m_pages[nPage];
394
395 m_pages.Clear();
396
397 return true;
398 }
399
400 // same as AddPage() but does it at given position
401 bool wxNotebook::InsertPage(size_t nPage,
402 wxNotebookPage *pPage,
403 const wxString& strText,
404 bool bSelect,
405 int WXUNUSED(imageId))
406 {
407 wxASSERT( pPage != NULL );
408 wxCHECK( IS_VALID_PAGE(nPage) || nPage == GetPageCount(), false );
409
410 m_tabView->AddTab(GetPageId(m_tabView, pPage), strText);
411
412 if (!bSelect)
413 pPage->Show(false);
414
415 // save the pointer to the page
416 m_pages.Insert(pPage, nPage);
417
418 if (bSelect)
419 {
420 // This will cause ChangePage to be called, via OnSelPage
421
422 m_tabView->SetTabSelection(GetPageId(m_tabView, pPage), true);
423 }
424
425 // some page must be selected: either this one or the first one if there is
426 // still no selection
427 if ( m_selection == -1 )
428 ChangePage(-1, 0);
429
430 RefreshLayout(false);
431
432 return true;
433 }
434
435 // ----------------------------------------------------------------------------
436 // wxNotebook callbacks
437 // ----------------------------------------------------------------------------
438
439 // @@@ OnSize() is used for setting the font when it's called for the first
440 // time because doing it in ::Create() doesn't work (for unknown reasons)
441 void wxNotebook::OnSize(wxSizeEvent& event)
442 {
443 static bool s_bFirstTime = true;
444 if ( s_bFirstTime ) {
445 // TODO: any first-time-size processing.
446 s_bFirstTime = false;
447 }
448
449 RefreshLayout();
450
451 // Processing continues to next OnSize
452 event.Skip();
453 }
454
455 // This was supposed to cure the non-display of the notebook
456 // until the user resizes the window.
457 // What's going on?
458 void wxNotebook::OnInternalIdle()
459 {
460 wxWindow::OnInternalIdle();
461
462 #if 0
463 static bool s_bFirstTime = true;
464 if ( s_bFirstTime ) {
465 /*
466 wxSize sz(GetSize());
467 sz.x ++;
468 SetSize(sz);
469 sz.x --;
470 SetSize(sz);
471 */
472
473 /*
474 wxSize sz(GetSize());
475 wxSizeEvent sizeEvent(sz, GetId());
476 sizeEvent.SetEventObject(this);
477 GetEventHandler()->ProcessEvent(sizeEvent);
478 Refresh();
479 */
480 s_bFirstTime = false;
481 }
482 #endif
483 }
484
485 // Implementation: calculate the layout of the view rect
486 // and resize the children if required
487 bool wxNotebook::RefreshLayout(bool force)
488 {
489 if (m_tabView)
490 {
491 wxRect oldRect = m_tabView->GetViewRect();
492
493 int cw, ch;
494 GetClientSize(& cw, & ch);
495
496 int tabHeight = m_tabView->GetTotalTabHeight();
497 wxRect rect;
498 rect.x = 4;
499 rect.y = tabHeight + 4;
500 rect.width = cw - 8;
501 rect.height = ch - 4 - rect.y ;
502
503 m_tabView->SetViewRect(rect);
504
505 m_tabView->LayoutTabs();
506
507 // Need to do it a 2nd time to get the tab height with
508 // the new view width, since changing the view width changes the
509 // tab layout.
510 tabHeight = m_tabView->GetTotalTabHeight();
511 rect.x = 4;
512 rect.y = tabHeight + 4;
513 rect.width = cw - 8;
514 rect.height = ch - 4 - rect.y ;
515
516 m_tabView->SetViewRect(rect);
517
518 m_tabView->LayoutTabs();
519
520 if (!force && (rect == oldRect))
521 return false;
522
523 // fit the notebook page to the tab control's display area
524
525 size_t nCount = m_pages.Count();
526 for ( size_t nPage = 0; nPage < nCount; nPage++ ) {
527 wxNotebookPage *pPage = m_pages[nPage];
528 wxRect clientRect = GetAvailableClientSize();
529 if (pPage->IsShown())
530 {
531 pPage->SetSize(clientRect.x, clientRect.y, clientRect.width, clientRect.height);
532 if ( pPage->GetAutoLayout() )
533 pPage->Layout();
534 }
535 }
536 Refresh();
537 }
538 return true;
539 }
540
541 void wxNotebook::OnSelChange(wxBookCtrlEvent& event)
542 {
543 // is it our tab control?
544 if ( event.GetEventObject() == this )
545 {
546 if (event.GetSelection() != m_selection)
547 ChangePage(event.GetOldSelection(), event.GetSelection());
548 }
549
550 // we want to give others a chance to process this message as well
551 event.Skip();
552 }
553
554 void wxNotebook::OnSetFocus(wxFocusEvent& event)
555 {
556 // set focus to the currently selected page if any
557 if ( m_selection != -1 )
558 m_pages[m_selection]->SetFocus();
559
560 event.Skip();
561 }
562
563 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
564 {
565 if ( event.IsWindowChange() )
566 {
567 // change pages
568 AdvanceSelection(event.GetDirection());
569 }
570 else {
571 // pass to the parent
572 if ( GetParent() )
573 {
574 event.SetCurrentFocus(this);
575 GetParent()->ProcessWindowEvent(event);
576 }
577 }
578 }
579
580 // ----------------------------------------------------------------------------
581 // wxNotebook base class virtuals
582 // ----------------------------------------------------------------------------
583
584 // override these 2 functions to do nothing: everything is done in OnSize
585
586 void wxNotebook::SetConstraintSizes(bool /* recurse */)
587 {
588 // don't set the sizes of the pages - their correct size is not yet known
589 wxControl::SetConstraintSizes(false);
590 }
591
592 bool wxNotebook::DoPhase(int /* nPhase */)
593 {
594 return true;
595 }
596
597 void wxNotebook::Command(wxCommandEvent& WXUNUSED(event))
598 {
599 wxFAIL_MSG(wxT("wxNotebook::Command not implemented"));
600 }
601
602 // ----------------------------------------------------------------------------
603 // wxNotebook helper functions
604 // ----------------------------------------------------------------------------
605
606 // hide the currently active panel and show the new one
607 void wxNotebook::ChangePage(int nOldSel, int nSel)
608 {
609 // cout << "ChangePage: " << nOldSel << ", " << nSel << "\n";
610 wxASSERT( nOldSel != nSel ); // impossible
611
612 if ( nOldSel != -1 ) {
613 m_pages[nOldSel]->Show(false);
614 m_pages[nOldSel]->Lower();
615 }
616
617 wxNotebookPage *pPage = m_pages[nSel];
618
619 wxRect clientRect = GetAvailableClientSize();
620 pPage->SetSize(clientRect.x, clientRect.y, clientRect.width, clientRect.height);
621
622 Refresh();
623
624 pPage->Show(true);
625 pPage->Raise();
626 pPage->SetFocus();
627
628 m_selection = nSel;
629 }
630
631 void wxNotebook::OnMouseEvent(wxMouseEvent& event)
632 {
633 if (m_tabView)
634 m_tabView->OnEvent(event);
635 }
636
637 void wxNotebook::OnPaint(wxPaintEvent& WXUNUSED(event) )
638 {
639 wxPaintDC dc(this);
640 if (m_tabView)
641 m_tabView->Draw(dc);
642 }
643
644 wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage) const
645 {
646 // MBN: since the total tab height is really a function of the
647 // width, this should really call
648 // GetTotalTabHeightPretendingWidthIs(), but the current
649 // implementation will suffice, provided the wxNotebook has been
650 // created with a sensible initial width.
651 return wxSize( sizePage.x + 12,
652 sizePage.y + m_tabView->GetTotalTabHeight() + 6 + 4 );
653 }
654
655 wxRect wxNotebook::GetAvailableClientSize()
656 {
657 int cw, ch;
658 GetClientSize(& cw, & ch);
659
660 int tabHeight = m_tabView->GetTotalTabHeight();
661
662 // TODO: these margins should be configurable.
663 wxRect rect;
664 rect.x = 6;
665 rect.y = tabHeight + 6;
666 rect.width = cw - 12;
667 rect.height = ch - 4 - rect.y ;
668
669 return rect;
670 }
671
672 /*
673 * wxNotebookTabView
674 */
675
676 IMPLEMENT_CLASS(wxNotebookTabView, wxTabView)
677
678 wxNotebookTabView::wxNotebookTabView(wxNotebook *notebook, long style)
679 : wxTabView(style), m_nextid(1)
680 {
681 m_notebook = notebook;
682
683 m_notebook->SetTabView(this);
684
685 SetWindow(m_notebook);
686 }
687
688 wxNotebookTabView::~wxNotebookTabView(void)
689 {
690 }
691
692 int wxNotebookTabView::GetId(wxNotebookPage *page)
693 {
694 int& id = m_pageToId[page];
695
696 if (!id)
697 {
698 id = m_nextid++;
699 m_idToPage[id] = page;
700 }
701
702 return id;
703 }
704
705 // Called when a tab is activated
706 void wxNotebookTabView::OnTabActivate(int activateId, int deactivateId)
707 {
708 if (!m_notebook)
709 return;
710
711 wxBookCtrlEvent event(wxEVT_NOTEBOOK_PAGE_CHANGED, m_notebook->GetId());
712
713 // Translate from wxTabView's ids (which aren't position-dependent)
714 // to wxNotebook's (which are).
715 wxNotebookPage* pActive = GetPage(activateId);
716 wxNotebookPage* pDeactive = GetPage(deactivateId);
717
718 int activatePos = m_notebook->FindPagePosition(pActive);
719 int deactivatePos = m_notebook->FindPagePosition(pDeactive);
720
721 event.SetEventObject(m_notebook);
722 event.SetSelection(activatePos);
723 event.SetOldSelection(deactivatePos);
724 m_notebook->GetEventHandler()->ProcessEvent(event);
725 }
726
727 // Allows Vetoing
728 bool wxNotebookTabView::OnTabPreActivate(int activateId, int deactivateId)
729 {
730 bool retval = true;
731
732 if (m_notebook)
733 {
734 wxBookCtrlEvent event(wxEVT_NOTEBOOK_PAGE_CHANGING, m_notebook->GetId());
735
736 // Translate from wxTabView's ids (which aren't position-dependent)
737 // to wxNotebook's (which are).
738 wxNotebookPage* pActive = GetPage(activateId);
739 wxNotebookPage* pDeactive = GetPage(deactivateId);
740
741 int activatePos = m_notebook->FindPagePosition(pActive);
742 int deactivatePos = m_notebook->FindPagePosition(pDeactive);
743
744 event.SetEventObject(m_notebook);
745 event.SetSelection(activatePos);
746 event.SetOldSelection(deactivatePos);
747 if (m_notebook->GetEventHandler()->ProcessEvent(event))
748 {
749 retval = event.IsAllowed();
750 }
751 }
752 return retval;
753 }
754
755 #endif // wxUSE_NOTEBOOK