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