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