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