]> git.saurik.com Git - wxWidgets.git/blob - src/univ/notebook.cpp
fixed 2 year old bug in wxBaseArray::operator=() (who the hell is/was RO who introduc...
[wxWidgets.git] / src / univ / notebook.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: univ/notebook.cpp
3 // Purpose: wxNotebook implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 01.02.01
7 // RCS-ID: $Id$
8 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "univnotebook.h"
22 #endif
23
24 #include "wx/wxprec.h"
25
26 #ifdef __BORLANDC__
27 #pragma hdrstop
28 #endif
29
30 #if wxUSE_NOTEBOOK
31
32 #include "wx/imaglist.h"
33 #include "wx/notebook.h"
34
35 #include "wx/spinbutt.h"
36
37 #include "wx/univ/renderer.h"
38
39 // ----------------------------------------------------------------------------
40 // macros
41 // ----------------------------------------------------------------------------
42
43 #define IS_VALID_PAGE(nPage) (((nPage) >= 0) && ((nPage) < GetPageCount()))
44
45 // ----------------------------------------------------------------------------
46 // constants
47 // ----------------------------------------------------------------------------
48
49 static const size_t INVALID_PAGE = (size_t)-1;
50
51 // ----------------------------------------------------------------------------
52 // private classes
53 // ----------------------------------------------------------------------------
54
55 class wxNotebookSpinBtn : public wxSpinButton
56 {
57 public:
58 wxNotebookSpinBtn(wxNotebook *nb)
59 : wxSpinButton(nb, -1,
60 wxDefaultPosition, wxDefaultSize,
61 nb->IsVertical() ? wxSP_VERTICAL : wxSP_HORIZONTAL)
62 {
63 m_nb = nb;
64 }
65
66 protected:
67 void OnSpin(wxSpinEvent& event)
68 {
69 m_nb->PerformAction(wxACTION_NOTEBOOK_GOTO, event.GetPosition());
70 }
71
72 private:
73 wxNotebook *m_nb;
74
75 DECLARE_EVENT_TABLE()
76 };
77
78 BEGIN_EVENT_TABLE(wxNotebookSpinBtn, wxSpinButton)
79 EVT_SPIN(-1, wxNotebookSpinBtn::OnSpin)
80 END_EVENT_TABLE()
81
82 // ============================================================================
83 // implementation
84 // ============================================================================
85
86 IMPLEMENT_DYNAMIC_CLASS(wxNotebook, wxControl)
87 IMPLEMENT_DYNAMIC_CLASS(wxNotebookEvent, wxCommandEvent)
88
89 // ----------------------------------------------------------------------------
90 // wxNotebook creation
91 // ----------------------------------------------------------------------------
92
93 void wxNotebook::Init()
94 {
95 m_sel = INVALID_PAGE;
96
97 m_heightTab =
98 m_widthMax = 0;
99
100 m_firstVisible =
101 m_lastVisible =
102 m_lastFullyVisible = 0;
103
104 m_offset = 0;
105
106 m_spinbtn = NULL;
107 }
108
109 bool wxNotebook::Create(wxWindow *parent,
110 wxWindowID id,
111 const wxPoint& pos,
112 const wxSize& size,
113 long style,
114 const wxString& name)
115 {
116 if ( !wxControl::Create(parent, id, pos, size, style,
117 wxDefaultValidator, name) )
118 return FALSE;
119
120 m_sizePad = GetRenderer()->GetTabPadding();
121
122 SetBestSize(size);
123
124 CreateInputHandler(wxINP_HANDLER_NOTEBOOK);
125
126 return TRUE;
127 }
128
129 // ----------------------------------------------------------------------------
130 // wxNotebook page titles and images
131 // ----------------------------------------------------------------------------
132
133 wxString wxNotebook::GetPageText(int nPage) const
134 {
135 wxCHECK_MSG( IS_VALID_PAGE(nPage), _T(""), _T("invalid notebook page") );
136
137 return m_titles[nPage];
138 }
139
140 bool wxNotebook::SetPageText(int nPage, const wxString& strText)
141 {
142 wxCHECK_MSG( IS_VALID_PAGE(nPage), FALSE, _T("invalid notebook page") );
143
144 if ( strText != m_titles[nPage] )
145 {
146 m_accels[nPage] = FindAccelIndex(strText, &m_titles[nPage]);
147
148 if ( FixedSizeTabs() )
149 {
150 // it's enough to just reresh this one
151 RefreshTab(nPage);
152 }
153 else // var width tabs
154 {
155 // we need to resize the tab to fit the new string
156 ResizeTab(nPage);
157 }
158 }
159
160 return TRUE;
161 }
162
163 int wxNotebook::GetPageImage(int nPage) const
164 {
165 wxCHECK_MSG( IS_VALID_PAGE(nPage), -1, _T("invalid notebook page") );
166
167 return m_images[nPage];
168 }
169
170 bool wxNotebook::SetPageImage(int nPage, int nImage)
171 {
172 wxCHECK_MSG( IS_VALID_PAGE(nPage), FALSE, _T("invalid notebook page") );
173
174 wxCHECK_MSG( m_imageList && nImage < m_imageList->GetImageCount(), FALSE,
175 _T("invalid image index in SetPageImage()") );
176
177 if ( nImage != m_images[nPage] )
178 {
179 // if the item didn't have an icon before or, on the contrary, did have
180 // it but has lost it now, its size will change - but if the icon just
181 // changes, it won't
182 bool tabSizeChanges = nImage == -1 || m_images[nPage] == -1;
183 m_images[nPage] = nImage;
184
185 if ( tabSizeChanges )
186 RefreshAllTabs();
187 else
188 RefreshTab(nPage);
189 }
190
191 return TRUE;
192 }
193
194 wxNotebook::~wxNotebook()
195 {
196 }
197
198 // ----------------------------------------------------------------------------
199 // wxNotebook page switching
200 // ----------------------------------------------------------------------------
201
202 int wxNotebook::SetSelection(int nPage)
203 {
204 wxCHECK_MSG( IS_VALID_PAGE(nPage), -1, _T("invalid notebook page") );
205
206 if ( (size_t)nPage == m_sel )
207 {
208 // don't do anything if there is nothing to do
209 return m_sel;
210 }
211
212 if ( m_sel != INVALID_PAGE )
213 {
214 RefreshTab(m_sel);
215
216 m_pages[m_sel]->Hide();
217 }
218
219 m_sel = nPage;
220
221 if ( m_sel != INVALID_PAGE ) // this is impossible - but test nevertheless
222 {
223 if ( HasSpinBtn() )
224 {
225 // keep it in sync
226 m_spinbtn->SetValue(m_sel);
227 }
228
229 if ( m_sel < m_firstVisible )
230 {
231 // selection is to the left of visible part of tabs
232 ScrollTo(m_sel);
233 }
234 else if ( m_sel > m_lastFullyVisible )
235 {
236 // selection is to the right of visible part of tabs
237 ScrollLastTo(m_sel);
238 }
239 else // we already see this tab
240 {
241 // no need to scroll
242 RefreshTab(m_sel);
243 }
244
245 m_pages[m_sel]->SetSize(GetPageRect());
246 m_pages[m_sel]->Show();
247 }
248
249 return m_sel;
250 }
251
252 void wxNotebook::ChangePage(int nPage)
253 {
254 wxCHECK_RET( IS_VALID_PAGE(nPage), _T("invalid notebook page") );
255
256 if ( (size_t)nPage == m_sel )
257 {
258 // nothing to do
259 return;
260 }
261
262 wxNotebookEvent event(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, m_windowId);
263 event.SetSelection(nPage);
264 event.SetOldSelection(m_sel);
265 event.SetEventObject(this);
266 if ( GetEventHandler()->ProcessEvent(event) && !event.IsAllowed() )
267 {
268 // program doesn't allow the page change
269 return;
270 }
271
272 SetSelection(nPage);
273
274 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
275 GetEventHandler()->ProcessEvent(event);
276 }
277
278 // ----------------------------------------------------------------------------
279 // wxNotebook pages adding/deleting
280 // ----------------------------------------------------------------------------
281
282 bool wxNotebook::InsertPage(int nPage,
283 wxNotebookPage *pPage,
284 const wxString& strText,
285 bool bSelect,
286 int imageId)
287 {
288 int nPages = GetPageCount();
289 wxCHECK_MSG( nPage == nPages || IS_VALID_PAGE(nPage), FALSE,
290 _T("invalid notebook page in InsertPage()") );
291
292 // modify the data
293 m_pages.Insert(pPage, nPage);
294
295 wxString label;
296 m_accels.Insert(FindAccelIndex(strText, &label), nPage);
297 m_titles.Insert(label, nPage);
298
299 m_images.Insert(imageId, nPage);
300
301 // cache the tab geometry here
302 wxSize sizeTab = CalcTabSize(nPage);
303
304 if ( sizeTab.y > m_heightTab )
305 m_heightTab = sizeTab.y;
306
307 if ( FixedSizeTabs() && sizeTab.x > m_widthMax )
308 m_widthMax = sizeTab.x;
309
310 m_widths.Insert(sizeTab.x, nPage);
311
312 // spin button may appear if we didn't have it before - but even if we did,
313 // its range should change, so update it unconditionally
314 UpdateSpinBtn();
315
316 // if the tab has just appeared, we have to relayout everything, otherwise
317 // it's enough to just redraw the tabs
318 if ( nPages == 0 )
319 {
320 // always select the first tab to have at least some selection
321 bSelect = TRUE;
322
323 Relayout();
324 }
325 else // not the first tab
326 {
327 RefreshAllTabs();
328 }
329
330 if ( bSelect )
331 {
332 SetSelection(nPage);
333 }
334 else // pages added to the notebook are initially hidden
335 {
336 pPage->Hide();
337 }
338
339 return TRUE;
340 }
341
342 bool wxNotebook::DeleteAllPages()
343 {
344 if ( !wxNotebookBase::DeleteAllPages() )
345 return FALSE;
346
347 // clear the other arrays as well
348 m_titles.Clear();
349 m_images.Clear();
350 m_accels.Clear();
351 m_widths.Clear();
352
353 // it is not valid any longer
354 m_sel = INVALID_PAGE;
355
356 // spin button is not needed any more
357 UpdateSpinBtn();
358
359 Relayout();
360
361 return TRUE;
362 }
363
364 wxNotebookPage *wxNotebook::DoRemovePage(int nPage)
365 {
366 wxCHECK_MSG( IS_VALID_PAGE(nPage), NULL, _T("invalid notebook page") );
367
368 wxNotebookPage *page = m_pages[nPage];
369 m_pages.RemoveAt(nPage);
370 m_titles.RemoveAt(nPage);
371 m_accels.RemoveAt(nPage);
372 m_widths.RemoveAt(nPage);
373 m_images.RemoveAt(nPage);
374
375 // the spin button might not be needed any more
376 if ( HasSpinBtn() )
377 {
378 UpdateSpinBtn();
379 }
380
381 int count = GetPageCount();
382 if ( count )
383 {
384 if ( m_sel == (size_t)nPage )
385 {
386 // avoid sending event to this page which doesn't exist in the
387 // notebook any more
388 m_sel = INVALID_PAGE;
389
390 SetSelection(nPage == count ? nPage - 1 : nPage);
391 }
392 else if ( m_sel > (size_t)nPage )
393 {
394 // no need to change selection, just adjust the index
395 m_sel--;
396 }
397 }
398 else // no more tabs left
399 {
400 m_sel = INVALID_PAGE;
401 }
402
403 // have to refresh everything
404 Relayout();
405
406 return page;
407 }
408
409 // ----------------------------------------------------------------------------
410 // wxNotebook drawing
411 // ----------------------------------------------------------------------------
412
413 void wxNotebook::RefreshCurrent()
414 {
415 if ( m_sel != INVALID_PAGE )
416 {
417 RefreshTab(m_sel);
418 }
419 }
420
421 void wxNotebook::RefreshTab(int page)
422 {
423 wxCHECK_RET( IS_VALID_PAGE(page), _T("invalid notebook page") );
424
425 wxRect rect = GetTabRect(page);
426 if ( (size_t)page == m_sel )
427 {
428 const wxSize indent = GetRenderer()->GetTabIndent();
429 rect.Inflate(indent.x, indent.y);
430 }
431
432 RefreshRect(rect);
433 }
434
435 void wxNotebook::RefreshAllTabs()
436 {
437 wxRect rect = GetAllTabsRect();
438 if ( rect.width || rect.height )
439 {
440 RefreshRect(rect);
441 }
442 //else: we don't have tabs at all
443 }
444
445 void wxNotebook::DoDrawTab(wxDC& dc, const wxRect& rect, size_t n)
446 {
447 wxBitmap bmp;
448 if ( HasImage(n) )
449 {
450 int image = m_images[n];
451
452 #ifdef __WXMSW__ // FIXME
453 int w, h;
454 m_imageList->GetSize(n, w, h);
455 bmp.Create(w, h);
456 wxMemoryDC dc;
457 dc.SelectObject(bmp);
458 dc.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
459 m_imageList->Draw(image, dc, 0, 0, wxIMAGELIST_DRAW_NORMAL, TRUE);
460 #else
461 bmp = *m_imageList->GetBitmap(image);
462 #endif
463 }
464
465 int flags = 0;
466 if ( n == m_sel )
467 {
468 flags |= wxCONTROL_SELECTED;
469
470 if ( IsFocused() )
471 flags |= wxCONTROL_FOCUSED;
472 }
473
474 GetRenderer()->DrawTab
475 (
476 dc,
477 rect,
478 GetTabOrientation(),
479 m_titles[n],
480 bmp,
481 flags,
482 m_accels[n]
483 );
484 }
485
486 void wxNotebook::DoDraw(wxControlRenderer *renderer)
487 {
488 //wxRect rectUpdate = GetUpdateClientRect(); -- unused
489
490 wxDC& dc = renderer->GetDC();
491 dc.SetFont(GetFont());
492 dc.SetTextForeground(GetForegroundColour());
493
494 // redraw the border - it's simpler to always do it instead of checking
495 // whether this needs to be done
496 GetRenderer()->DrawBorder(dc, wxBORDER_RAISED, GetPagePart());
497
498 // avoid overwriting the spin button
499 if ( HasSpinBtn() )
500 {
501 wxRect rectTabs = GetAllTabsRect();
502 wxSize sizeSpinBtn = m_spinbtn->GetSize();
503
504 if ( IsVertical() )
505 rectTabs.height -= sizeSpinBtn.y;
506 else
507 rectTabs.width -= sizeSpinBtn.x;
508
509 dc.SetClippingRegion(rectTabs);
510 }
511
512 wxRect rect = GetTabsPart();
513 bool isVertical = IsVertical();
514
515 wxRect rectSel;
516 for ( size_t n = m_firstVisible; n < m_lastVisible; n++ )
517 {
518 GetTabSize(n, &rect.width, &rect.height);
519
520 if ( n == m_sel )
521 {
522 // don't redraw it now as this tab has to be drawn over the other
523 // ones as it takes more place and spills over to them
524 rectSel = rect;
525 }
526 else // not selected tab
527 {
528 // unfortunately we can't do this because the selected tab hangs
529 // over its neighbours and so we might need to refresh more tabs -
530 // of course, we could still avoid rereshing some of them with more
531 // complicated checks, but it doesn't seem too bad to refresh all
532 // of them, I still don't see flicker, so leaving as is for now
533
534 //if ( rectUpdate.Intersects(rect) )
535 {
536 DoDrawTab(dc, rect, n);
537 }
538 //else: doesn't need to be refreshed
539 }
540
541 // move the rect to the next tab
542 if ( isVertical )
543 rect.y += rect.height;
544 else
545 rect.x += rect.width;
546 }
547
548 // now redraw the selected tab
549 if ( rectSel.width )
550 {
551 DoDrawTab(dc, rectSel, m_sel);
552 }
553 }
554
555 // ----------------------------------------------------------------------------
556 // wxNotebook geometry
557 // ----------------------------------------------------------------------------
558
559 int wxNotebook::HitTest(const wxPoint& pt) const
560 {
561 // first check that it is in this window at all
562 if ( !GetClientRect().Inside(pt) )
563 {
564 return -1;
565 }
566
567 wxRect rectTabs = GetAllTabsRect();
568
569 switch ( GetTabOrientation() )
570 {
571 default:
572 wxFAIL_MSG(_T("unknown tab orientation"));
573 // fall through
574
575 case wxTOP:
576 if ( pt.y > rectTabs.GetBottom() )
577 return -1;
578 break;
579
580 case wxBOTTOM:
581 if ( pt.y < rectTabs.y )
582 return -1;
583 break;
584
585 case wxLEFT:
586 if ( pt.x > rectTabs.GetRight() )
587 return -1;
588 break;
589
590 case wxRIGHT:
591 if ( pt.x < rectTabs.x )
592 return -1;
593 break;
594 }
595
596 for ( size_t n = m_firstVisible; n < m_lastVisible; n++ )
597 {
598 GetTabSize(n, &rectTabs.width, &rectTabs.height);
599
600 if ( rectTabs.Inside(pt) )
601 return n;
602
603 // move the rectTabs to the next tab
604 if ( IsVertical() )
605 rectTabs.y += rectTabs.height;
606 else
607 rectTabs.x += rectTabs.width;
608 }
609
610 return -1;
611 }
612
613 bool wxNotebook::IsVertical() const
614 {
615 wxDirection dir = GetTabOrientation();
616
617 return dir == wxLEFT || dir == wxRIGHT;
618 }
619
620 wxDirection wxNotebook::GetTabOrientation() const
621 {
622 long style = GetWindowStyle();
623 if ( style & wxNB_BOTTOM )
624 return wxBOTTOM;
625 else if ( style & wxNB_RIGHT )
626 return wxRIGHT;
627 else if ( style & wxNB_LEFT )
628 return wxLEFT;
629
630 // wxNB_TOP == 0 so we don't have to test for it
631 return wxTOP;
632 }
633
634 wxRect wxNotebook::GetTabRect(int page) const
635 {
636 wxRect rect;
637 wxCHECK_MSG( IS_VALID_PAGE(page), rect, _T("invalid notebook page") );
638
639 // calc the size of this tab and of the preceding ones
640 wxCoord widthThis, widthBefore;
641 if ( FixedSizeTabs() )
642 {
643 widthThis = m_widthMax;
644 widthBefore = page*m_widthMax;
645 }
646 else
647 {
648 widthBefore = 0;
649 for ( int n = 0; n < page; n++ )
650 {
651 widthBefore += m_widths[n];
652 }
653
654 widthThis = m_widths[page];
655 }
656
657 rect = GetTabsPart();
658 if ( IsVertical() )
659 {
660 rect.y += widthBefore - m_offset;
661 rect.height = widthThis;
662 }
663 else // horz
664 {
665 rect.x += widthBefore - m_offset;
666 rect.width = widthThis;
667 }
668
669 return rect;
670 }
671
672 wxRect wxNotebook::GetAllTabsRect() const
673 {
674 wxRect rect;
675
676 if ( GetPageCount() )
677 {
678 const wxSize indent = GetRenderer()->GetTabIndent();
679 wxSize size = GetClientSize();
680
681 if ( IsVertical() )
682 {
683 rect.width = m_heightTab + indent.x;
684 rect.x = GetTabOrientation() == wxLEFT ? 0 : size.x - rect.width;
685 rect.y = 0;
686 rect.height = size.y;
687 }
688 else // horz
689 {
690 rect.x = 0;
691 rect.width = size.x;
692 rect.height = m_heightTab + indent.y;
693 rect.y = GetTabOrientation() == wxTOP ? 0 : size.y - rect.height;
694 }
695 }
696 //else: no pages
697
698 return rect;
699 }
700
701 wxRect wxNotebook::GetTabsPart() const
702 {
703 wxRect rect = GetAllTabsRect();
704
705 wxDirection dir = GetTabOrientation();
706
707 const wxSize indent = GetRenderer()->GetTabIndent();
708 if ( IsVertical() )
709 {
710 rect.x += indent.y;
711 rect.y += indent.x;
712 }
713 else // horz
714 {
715 rect.x += indent.x;
716 if ( dir == wxTOP )
717 {
718 rect.y += indent.y;
719 rect.height -= indent.y;
720 }
721 else // wxBOTTOM
722 {
723 rect.height -= indent.y;
724 }
725 }
726
727 return rect;
728 }
729
730 void wxNotebook::GetTabSize(int page, wxCoord *w, wxCoord *h) const
731 {
732 wxCHECK_RET( w && h, _T("NULL pointer in GetTabSize") );
733
734 if ( IsVertical() )
735 {
736 // width and height have inverted meaning
737 wxCoord *tmp = w;
738 w = h;
739 h = tmp;
740 }
741
742 // height is always fixed
743 *h = m_heightTab;
744
745 // width may also be fixed and be the same for all tabs
746 *w = GetTabWidth(page);
747 }
748
749 void wxNotebook::SetTabSize(const wxSize& sz)
750 {
751 wxCHECK_RET( FixedSizeTabs(), _T("SetTabSize() ignored") );
752
753 if ( IsVertical() )
754 {
755 m_heightTab = sz.x;
756 m_widthMax = sz.y;
757 }
758 else // horz
759 {
760 m_widthMax = sz.x;
761 m_heightTab = sz.y;
762 }
763 }
764
765 wxSize wxNotebook::CalcTabSize(int page) const
766 {
767 // NB: don't use m_widthMax, m_heightTab or m_widths here because this
768 // method is called to calculate them
769
770 wxSize size;
771
772 wxCHECK_MSG( IS_VALID_PAGE(page), size, _T("invalid notebook page") );
773
774 GetTextExtent(m_titles[page], &size.x, &size.y);
775
776 if ( HasImage(page) )
777 {
778 wxSize sizeImage;
779 m_imageList->GetSize(m_images[page], sizeImage.x, sizeImage.y);
780
781 size.x += sizeImage.x + 5; // FIXME: hard coded margin
782
783 if ( sizeImage.y > size.y )
784 size.y = sizeImage.y;
785 }
786
787 size.x += 2*m_sizePad.x;
788 size.y += 2*m_sizePad.y;
789
790 return size;
791 }
792
793 void wxNotebook::ResizeTab(int page)
794 {
795 wxSize sizeTab = CalcTabSize(page);
796
797 // we only need full relayout if the page size changes
798 bool needsRelayout = FALSE;
799
800 if ( IsVertical() )
801 {
802 // swap coordinates
803 wxCoord tmp = sizeTab.x;
804 sizeTab.x = sizeTab.y;
805 sizeTab.y = tmp;
806 }
807
808 if ( sizeTab.y > m_heightTab )
809 {
810 needsRelayout = TRUE;
811
812 m_heightTab = sizeTab.y;
813 }
814
815 m_widths[page] = sizeTab.x;
816
817 if ( sizeTab.x > m_widthMax )
818 m_widthMax = sizeTab.x;
819
820 // the total of the tabs has changed too
821 UpdateSpinBtn();
822
823 if ( needsRelayout )
824 Relayout();
825 else
826 RefreshAllTabs();
827 }
828
829 void wxNotebook::SetPadding(const wxSize& padding)
830 {
831 if ( padding != m_sizePad )
832 {
833 m_sizePad = padding;
834
835 Relayout();
836 }
837 }
838
839 void wxNotebook::Relayout()
840 {
841 if ( GetPageCount() )
842 {
843 RefreshAllTabs();
844
845 UpdateSpinBtn();
846
847 if ( m_sel != INVALID_PAGE )
848 {
849 // resize the currently shown page
850 wxRect rectPage = GetPageRect();
851
852 m_pages[m_sel]->SetSize(rectPage);
853
854 // also scroll it into view if needed (note that m_lastVisible
855 // was updated by the call to UpdateSpinBtn() above, this is why it
856 // is needed here)
857 if ( HasSpinBtn() )
858 {
859 if ( m_sel < m_firstVisible )
860 {
861 // selection is to the left of visible part of tabs
862 ScrollTo(m_sel);
863 }
864 else if ( m_sel > m_lastFullyVisible )
865 {
866 // selection is to the right of visible part of tabs
867 ScrollLastTo(m_sel);
868 }
869 }
870 }
871 }
872 else // we have no pages
873 {
874 // just refresh everything
875 Refresh();
876 }
877 }
878
879 wxRect wxNotebook::GetPagePart() const
880 {
881 wxRect rectPage = GetClientRect();
882
883 if ( GetPageCount() )
884 {
885 wxRect rectTabs = GetAllTabsRect();
886 wxDirection dir = GetTabOrientation();
887 if ( IsVertical() )
888 {
889 rectPage.width -= rectTabs.width;
890 if ( dir == wxLEFT )
891 rectPage.x += rectTabs.width;
892 }
893 else // horz
894 {
895 rectPage.height -= rectTabs.height;
896 if ( dir == wxTOP )
897 rectPage.y += rectTabs.height;
898 }
899 }
900 //else: no pages at all
901
902 return rectPage;
903 }
904
905 wxRect wxNotebook::GetPageRect() const
906 {
907 wxRect rect = GetPagePart();
908
909 // leave space for the border
910 wxRect rectBorder = GetRenderer()->GetBorderDimensions(wxBORDER_RAISED);
911
912 // FIXME: hardcoded +2!
913 rect.Inflate(-(rectBorder.x + rectBorder.width + 2),
914 -(rectBorder.y + rectBorder.height + 2));
915
916 return rect;
917 }
918
919 wxSize wxNotebook::GetSizeForPage(const wxSize& size) const
920 {
921 wxSize sizeNb = size;
922 wxRect rect = GetAllTabsRect();
923 if ( IsVertical() )
924 sizeNb.x += rect.width;
925 else
926 sizeNb.y += rect.height;
927
928 return sizeNb;
929 }
930
931 void wxNotebook::SetPageSize(const wxSize& size)
932 {
933 SetClientSize(GetSizeForPage(size));
934 }
935
936 wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage)
937 {
938 return AdjustSize(GetSizeForPage(sizePage));
939 }
940
941 // ----------------------------------------------------------------------------
942 // wxNotebook spin button
943 // ----------------------------------------------------------------------------
944
945 bool wxNotebook::HasSpinBtn() const
946 {
947 return m_spinbtn && m_spinbtn->IsShown();
948 }
949
950 void wxNotebook::CalcLastVisibleTab()
951 {
952 bool isVertical = IsVertical();
953
954 wxCoord width = GetClientSize().x;
955
956 wxRect rect = GetTabsPart();
957
958 size_t count = GetPageCount();
959
960 wxCoord widthLast = 0;
961 size_t n;
962 for ( n = m_firstVisible; n < count; n++ )
963 {
964 GetTabSize(n, &rect.width, &rect.height);
965 if ( rect.GetRight() > width )
966 {
967 break;
968 }
969
970 // remember it to use below
971 widthLast = rect.GetRight();
972
973 // move the rect to the next tab
974 if ( isVertical )
975 rect.y += rect.height;
976 else
977 rect.x += rect.width;
978 }
979
980 if ( n == m_firstVisible )
981 {
982 // even the first tab isn't fully visible - but still pretend it is as
983 // we have to show something
984 m_lastFullyVisible = m_firstVisible;
985 }
986 else // more than one tab visible
987 {
988 m_lastFullyVisible = n - 1;
989
990 // but is it really fully visible? it shouldn't overlap with the spin
991 // button if it is present (again, even if the first button does
992 // overlap with it, we pretend that it doesn't as there is not much
993 // else we can do)
994 if ( (m_lastFullyVisible > m_firstVisible) && HasSpinBtn() )
995 {
996 // adjust width to be the width before the spin button
997 wxSize sizeSpinBtn = m_spinbtn->GetSize();
998 if ( IsVertical() )
999 width -= sizeSpinBtn.y;
1000 else
1001 width -= sizeSpinBtn.x;
1002
1003 if ( widthLast > width )
1004 {
1005 // the last button overlaps with spin button, so take he
1006 // previous one
1007 m_lastFullyVisible--;
1008 }
1009 }
1010 }
1011
1012 if ( n == count )
1013 {
1014 // everything is visible
1015 m_lastVisible = n;
1016 }
1017 else
1018 {
1019 // this tab is still (partially) visible, so m_lastVisible is the
1020 // next tab (remember, this is "exclusive" last)
1021 m_lastVisible = n + 1;
1022
1023 }
1024 }
1025
1026 void wxNotebook::UpdateSpinBtn()
1027 {
1028 // first decide if we need a spin button
1029 bool allTabsShown;
1030
1031 size_t count = (size_t)GetPageCount();
1032 if ( count == 0 )
1033 {
1034 // this case is special, get rid of it immediately: everything is
1035 // visible and we don't need any spin buttons
1036 allTabsShown = TRUE;
1037
1038 // have to reset them manually as we don't call CalcLastVisibleTab()
1039 m_firstVisible =
1040 m_lastVisible =
1041 m_lastFullyVisible = 0;
1042 }
1043 else
1044 {
1045 CalcLastVisibleTab();
1046
1047 // if all tabs after the first visible one are shown, it doesn't yet
1048 // mean that all tabs are shown - so we go backwards until we arrive to
1049 // the beginning (then all tabs are indeed shown) or find a tab such
1050 // that not all tabs after it are shown
1051 while ( (m_lastFullyVisible == count - 1) && (m_firstVisible > 0) )
1052 {
1053 // this is equivalent to ScrollTo(m_firstVisible - 1) but more
1054 // efficient
1055 m_offset -= GetTabWidth(m_firstVisible--);
1056
1057 // reclaculate after m_firstVisible change
1058 CalcLastVisibleTab();
1059 }
1060
1061 allTabsShown = m_lastFullyVisible == count - 1;
1062 }
1063
1064 if ( !allTabsShown )
1065 {
1066 if ( !m_spinbtn )
1067 {
1068 // create it once only
1069 m_spinbtn = new wxNotebookSpinBtn(this);
1070
1071 // set the correct value to keep it in sync
1072 m_spinbtn->SetValue(m_sel);
1073 }
1074
1075 // position it correctly
1076 PositionSpinBtn();
1077
1078 // and show it
1079 m_spinbtn->Show();
1080
1081 // also set/update the range
1082 m_spinbtn->SetRange(0, count - 1);
1083
1084 // update m_lastFullyVisible once again as it might have changed
1085 // because the spin button appeared
1086 //
1087 // FIXME: might do it more efficiently
1088 CalcLastVisibleTab();
1089 }
1090 else // all tabs are visible, we don't need spin button
1091 {
1092 if ( m_spinbtn )
1093 {
1094 m_spinbtn->Hide();
1095 }
1096 }
1097 }
1098
1099 void wxNotebook::PositionSpinBtn()
1100 {
1101 if ( !m_spinbtn )
1102 return;
1103
1104 wxCoord wBtn, hBtn;
1105 m_spinbtn->GetSize(&wBtn, &hBtn);
1106
1107 wxRect rectTabs = GetAllTabsRect();
1108
1109 wxCoord x, y;
1110 switch ( GetTabOrientation() )
1111 {
1112 default:
1113 wxFAIL_MSG(_T("unknown tab orientation"));
1114 // fall through
1115
1116 case wxTOP:
1117 x = rectTabs.GetRight() - wBtn;
1118 y = rectTabs.GetBottom() - hBtn;
1119 break;
1120
1121 case wxBOTTOM:
1122 x = rectTabs.GetRight() - wBtn;
1123 y = rectTabs.GetTop();
1124 break;
1125
1126 case wxLEFT:
1127 x = rectTabs.GetRight() - wBtn;
1128 y = rectTabs.GetBottom() - hBtn;
1129 break;
1130
1131 case wxRIGHT:
1132 x = rectTabs.GetLeft();
1133 y = rectTabs.GetBottom() - hBtn;
1134 break;
1135 }
1136
1137 m_spinbtn->Move(x, y);
1138 }
1139
1140 // ----------------------------------------------------------------------------
1141 // wxNotebook scrolling
1142 // ----------------------------------------------------------------------------
1143
1144 void wxNotebook::ScrollTo(int page)
1145 {
1146 wxCHECK_RET( IS_VALID_PAGE(page), _T("invalid notebook page") );
1147
1148 // set the first visible tab and offset (easy)
1149 m_firstVisible = (size_t)page;
1150 m_offset = 0;
1151 for ( size_t n = 0; n < m_firstVisible; n++ )
1152 {
1153 m_offset += GetTabWidth(n);
1154 }
1155
1156 // find the last visible tab too
1157 CalcLastVisibleTab();
1158
1159 RefreshAllTabs();
1160 }
1161
1162 void wxNotebook::ScrollLastTo(int page)
1163 {
1164 wxCHECK_RET( IS_VALID_PAGE(page), _T("invalid notebook page") );
1165
1166 // go backwards until we find the first tab which can be made visible
1167 // without hiding the given one
1168 wxSize size = GetClientSize();
1169 wxCoord widthAll = IsVertical() ? size.y : size.x,
1170 widthTabs = GetTabWidth(page);
1171
1172 // the total width is less than the width of the window if we have the spin
1173 // button
1174 if ( HasSpinBtn() )
1175 {
1176 wxSize sizeSpinBtn = m_spinbtn->GetSize();
1177 if ( IsVertical() )
1178 widthAll -= sizeSpinBtn.y;
1179 else
1180 widthAll -= sizeSpinBtn.x;
1181 }
1182
1183 m_firstVisible = page;
1184 while ( (m_firstVisible > 0) && (widthTabs <= widthAll) )
1185 {
1186 widthTabs += GetTabWidth(--m_firstVisible);
1187 }
1188
1189 if ( widthTabs > widthAll )
1190 {
1191 // take one step back (that it is forward) if we can
1192 if ( m_firstVisible < (size_t)GetPageCount() - 1 )
1193 m_firstVisible++;
1194 }
1195
1196 // go to it
1197 ScrollTo(m_firstVisible);
1198
1199 // consitency check: the page we were asked to show should be shown
1200 wxASSERT_MSG( (size_t)page < m_lastVisible, _T("bug in ScrollLastTo") );
1201 }
1202
1203 // ----------------------------------------------------------------------------
1204 // wxNotebook sizing/moving
1205 // ----------------------------------------------------------------------------
1206
1207 wxSize wxNotebook::DoGetBestClientSize() const
1208 {
1209 // calculate the max page size
1210 wxSize size(0, 0);
1211
1212 size_t count = GetPageCount();
1213 if ( count )
1214 {
1215 for ( size_t n = 0; n < count; n++ )
1216 {
1217 wxSize sizePage = m_pages[n]->GetSize();
1218
1219 if ( size.x < sizePage.x )
1220 size.x = sizePage.x;
1221 if ( size.y < sizePage.y )
1222 size.y = sizePage.y;
1223 }
1224 }
1225 else // no pages
1226 {
1227 // use some arbitrary default size
1228 size.x =
1229 size.y = 100;
1230 }
1231
1232 return GetSizeForPage(size);
1233 }
1234
1235 void wxNotebook::DoMoveWindow(int x, int y, int width, int height)
1236 {
1237 wxControl::DoMoveWindow(x, y, width, height);
1238
1239 // move the spin ctrl too (NOP if it doesn't exist)
1240 PositionSpinBtn();
1241 }
1242
1243 void wxNotebook::DoSetSize(int x, int y,
1244 int width, int height,
1245 int sizeFlags)
1246 {
1247 wxControl::DoSetSize(x, y, width, height, sizeFlags);
1248
1249 Relayout();
1250 }
1251
1252 // ----------------------------------------------------------------------------
1253 // wxNotebook input processing
1254 // ----------------------------------------------------------------------------
1255
1256 bool wxNotebook::PerformAction(const wxControlAction& action,
1257 long numArg,
1258 const wxString& strArg)
1259 {
1260 if ( action == wxACTION_NOTEBOOK_NEXT )
1261 ChangePage(GetNextPage(TRUE));
1262 else if ( action == wxACTION_NOTEBOOK_PREV )
1263 ChangePage(GetNextPage(FALSE));
1264 else if ( action == wxACTION_NOTEBOOK_GOTO )
1265 ChangePage((int)numArg);
1266 else
1267 return wxControl::PerformAction(action, numArg, strArg);
1268
1269 return TRUE;
1270 }
1271
1272 // ----------------------------------------------------------------------------
1273 // wxStdNotebookInputHandler
1274 // ----------------------------------------------------------------------------
1275
1276 wxStdNotebookInputHandler::wxStdNotebookInputHandler(wxInputHandler *inphand)
1277 : wxStdInputHandler(inphand)
1278 {
1279 }
1280
1281 bool wxStdNotebookInputHandler::HandleKey(wxControl *control,
1282 const wxKeyEvent& event,
1283 bool pressed)
1284 {
1285 // ignore the key releases
1286 if ( pressed )
1287 {
1288 wxNotebook *notebook = wxStaticCast(control, wxNotebook);
1289
1290 int page = 0;
1291 wxControlAction action;
1292 switch ( event.GetKeyCode() )
1293 {
1294 case WXK_UP:
1295 if ( notebook->IsVertical() )
1296 action = wxACTION_NOTEBOOK_PREV;
1297 break;
1298
1299 case WXK_LEFT:
1300 if ( !notebook->IsVertical() )
1301 action = wxACTION_NOTEBOOK_PREV;
1302 break;
1303
1304 case WXK_DOWN:
1305 if ( notebook->IsVertical() )
1306 action = wxACTION_NOTEBOOK_NEXT;
1307 break;
1308
1309 case WXK_RIGHT:
1310 if ( !notebook->IsVertical() )
1311 action = wxACTION_NOTEBOOK_NEXT;
1312 break;
1313
1314 case WXK_HOME:
1315 action = wxACTION_NOTEBOOK_GOTO;
1316 // page = 0; -- already has this value
1317 break;
1318
1319 case WXK_END:
1320 action = wxACTION_NOTEBOOK_GOTO;
1321 page = notebook->GetPageCount() - 1;
1322 break;
1323 }
1324
1325 if ( !!action )
1326 {
1327 return control->PerformAction(action, page);
1328 }
1329 }
1330
1331 return wxStdInputHandler::HandleKey(control, event, pressed);
1332 }
1333
1334 bool wxStdNotebookInputHandler::HandleMouse(wxControl *control,
1335 const wxMouseEvent& event)
1336 {
1337 if ( event.ButtonDown(1) )
1338 {
1339 wxNotebook *notebook = wxStaticCast(control, wxNotebook);
1340 int page = notebook->HitTest(event.GetPosition());
1341 if ( page != -1 )
1342 {
1343 control->PerformAction(wxACTION_NOTEBOOK_GOTO, page);
1344
1345 return FALSE;
1346 }
1347 }
1348
1349 return wxStdInputHandler::HandleMouse(control, event);
1350 }
1351
1352 bool wxStdNotebookInputHandler::HandleMouseMove(wxControl *control,
1353 const wxMouseEvent& event)
1354 {
1355 return wxStdInputHandler::HandleMouseMove(control, event);
1356 }
1357
1358 bool wxStdNotebookInputHandler::HandleFocus(wxControl *control,
1359 const wxFocusEvent& event)
1360 {
1361 HandleFocusChange(control);
1362
1363 return FALSE;
1364 }
1365
1366 bool wxStdNotebookInputHandler::HandleActivation(wxControl *control,
1367 bool WXUNUSED(activated))
1368 {
1369 // we react to the focus change in the same way as to the [de]activation
1370 HandleFocusChange(control);
1371
1372 return FALSE;
1373 }
1374
1375 void wxStdNotebookInputHandler::HandleFocusChange(wxControl *control)
1376 {
1377 wxNotebook *notebook = wxStaticCast(control, wxNotebook);
1378 notebook->RefreshCurrent();
1379 }
1380
1381 #endif // wxUSE_NOTEBOOK
1382