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