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