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:
wxNotebookSpinBtn(wxNotebook * nb)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:
OnSpin(wxSpinEvent & event)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 
BEGIN_EVENT_TABLE(wxNotebookSpinBtn,wxSpinButton)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 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)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 
GetPageText(size_t nPage) const152 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 
SetPageText(size_t nPage,const wxString & strText)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 
GetPageImage(size_t nPage) const182 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 
SetPageImage(size_t nPage,int nImage)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 
~wxNotebook()213 wxNotebook::~wxNotebook()
214 {
215 }
216 
217 // ----------------------------------------------------------------------------
218 // wxNotebook page switching
219 // ----------------------------------------------------------------------------
220 
DoSetSelection(size_t nPage,int flags)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 
InsertPage(size_t nPage,wxNotebookPage * pPage,const wxString & strText,bool bSelect,int imageId)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 
DeleteAllPages()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 
DoRemovePage(size_t nPage)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 
RefreshCurrent()431 void wxNotebook::RefreshCurrent()
432 {
433     if ( m_selection != wxNOT_FOUND )
434     {
435         RefreshTab(m_selection);
436     }
437 }
438 
RefreshTab(int page,bool forceSelected)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 
RefreshAllTabs()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 
DoDrawTab(wxDC & dc,const wxRect & rect,size_t n)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 
DoDraw(wxControlRenderer * renderer)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 
HitTest(const wxPoint & pt,long * flags) const592 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 
IsVertical() const657 bool wxNotebook::IsVertical() const
658 {
659     wxDirection dir = GetTabOrientation();
660 
661     return dir == wxLEFT || dir == wxRIGHT;
662 }
663 
GetTabOrientation() const664 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 
GetTabRect(int page) const678 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 
GetAllTabsRect() const716 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 
GetTabsPart() const745 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 
GetTabSize(int page,wxCoord * w,wxCoord * h) const782 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 
SetTabSize(const wxSize & sz)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 
CalcTabSize(int page) const817 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 
ResizeTab(int page)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 
SetPadding(const wxSize & padding)881 void wxNotebook::SetPadding(const wxSize& padding)
882 {
883     if ( padding != m_sizePad )
884     {
885         m_sizePad = padding;
886 
887         Relayout();
888     }
889 }
890 
Relayout()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 
GetPagePart() const932 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 
GetPageRect() const958 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 
GetSizeForPage(const wxSize & size) const972 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 
SetPageSize(const wxSize & size)984 void wxNotebook::SetPageSize(const wxSize& size)
985 {
986     SetClientSize(GetSizeForPage(size));
987 }
988 
CalcSizeFromPage(const wxSize & sizePage) const989 wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage) const
990 {
991     return AdjustSize(GetSizeForPage(sizePage));
992 }
993 
994 // ----------------------------------------------------------------------------
995 // wxNotebook spin button
996 // ----------------------------------------------------------------------------
997 
HasSpinBtn() const998 bool wxNotebook::HasSpinBtn() const
999 {
1000     return m_spinbtn && m_spinbtn->IsShown();
1001 }
1002 
CalcLastVisibleTab()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 
UpdateSpinBtn()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 
PositionSpinBtn()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 
ScrollTo(size_t page)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 
ScrollLastTo(size_t page)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 
DoMoveWindow(int x,int y,int width,int height)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 
DoSetSize(int x,int y,int width,int height,int sizeFlags)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 
PerformAction(const wxControlAction & action,long numArg,const wxString & strArg)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 */
GetStdInputHandler(wxInputHandler * handlerDef)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 
wxStdNotebookInputHandler(wxInputHandler * inphand)1314 wxStdNotebookInputHandler::wxStdNotebookInputHandler(wxInputHandler *inphand)
1315                          : wxStdInputHandler(inphand)
1316 {
1317 }
1318 
HandleKey(wxInputConsumer * consumer,const wxKeyEvent & event,bool pressed)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 
HandleMouse(wxInputConsumer * consumer,const wxMouseEvent & event)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 
HandleMouseMove(wxInputConsumer * consumer,const wxMouseEvent & event)1390 bool wxStdNotebookInputHandler::HandleMouseMove(wxInputConsumer *consumer,
1391                                                 const wxMouseEvent& event)
1392 {
1393     return wxStdInputHandler::HandleMouseMove(consumer, event);
1394 }
1395 
1396 bool
HandleFocus(wxInputConsumer * consumer,const wxFocusEvent & WXUNUSED (event))1397 wxStdNotebookInputHandler::HandleFocus(wxInputConsumer *consumer,
1398                                        const wxFocusEvent& WXUNUSED(event))
1399 {
1400     HandleFocusChange(consumer);
1401 
1402     return false;
1403 }
1404 
HandleActivation(wxInputConsumer * consumer,bool WXUNUSED (activated))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 
HandleFocusChange(wxInputConsumer * consumer)1414 void wxStdNotebookInputHandler::HandleFocusChange(wxInputConsumer *consumer)
1415 {
1416     wxNotebook *notebook = wxStaticCast(consumer->GetInputWindow(), wxNotebook);
1417     notebook->RefreshCurrent();
1418 }
1419 
1420 #endif // wxUSE_NOTEBOOK
1421