]> git.saurik.com Git - wxWidgets.git/blame - src/univ/notebook.cpp
update copyright year to 2009 (closes #10319)
[wxWidgets.git] / src / univ / notebook.cpp
CommitLineData
1e6feb95 1/////////////////////////////////////////////////////////////////////////////
90c0f5a9 2// Name: src/univ/notebook.cpp
1e6feb95
VZ
3// Purpose: wxNotebook implementation
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 01.02.01
7// RCS-ID: $Id$
442b35b5 8// Copyright: (c) 2001 SciTech Software, Inc. (www.scitechsoft.com)
65571936 9// Licence: wxWindows licence
1e6feb95
VZ
10/////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
1e6feb95
VZ
20#include "wx/wxprec.h"
21
22#ifdef __BORLANDC__
23 #pragma hdrstop
24#endif
25
26#if wxUSE_NOTEBOOK
27
1e6feb95 28#include "wx/notebook.h"
f38924e8
WS
29
30#ifndef WX_PRECOMP
31 #include "wx/dcmemory.h"
32#endif
33
34#include "wx/imaglist.h"
1e6feb95
VZ
35#include "wx/spinbutt.h"
36
37#include "wx/univ/renderer.h"
38
9467bdb7
VZ
39// ----------------------------------------------------------------------------
40// wxStdNotebookInputHandler: translates SPACE and ENTER keys and the left mouse
41// click into button press/release actions
42// ----------------------------------------------------------------------------
43
44class WXDLLEXPORT wxStdNotebookInputHandler : public wxStdInputHandler
45{
46public:
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
58protected:
59 void HandleFocusChange(wxInputConsumer *consumer);
60};
61
1e6feb95
VZ
62// ----------------------------------------------------------------------------
63// macros
64// ----------------------------------------------------------------------------
65
8a39593e
JS
66#if 0
67// due to unsigned type nPage is always >= 0
34a0c9f4 68#define IS_VALID_PAGE(nPage) (((nPage) >= 0) && ((size_t(nPage)) < GetPageCount()))
8a39593e 69#else
32b13913 70#define IS_VALID_PAGE(nPage) (((size_t)nPage) < GetPageCount())
8a39593e 71#endif
1e6feb95
VZ
72
73// ----------------------------------------------------------------------------
74// constants
75// ----------------------------------------------------------------------------
76
77static const size_t INVALID_PAGE = (size_t)-1;
78
79// ----------------------------------------------------------------------------
80// private classes
81// ----------------------------------------------------------------------------
82
83class wxNotebookSpinBtn : public wxSpinButton
84{
85public:
86 wxNotebookSpinBtn(wxNotebook *nb)
a290fa5a 87 : wxSpinButton(nb, wxID_ANY,
1e6feb95
VZ
88 wxDefaultPosition, wxDefaultSize,
89 nb->IsVertical() ? wxSP_VERTICAL : wxSP_HORIZONTAL)
90 {
91 m_nb = nb;
92 }
93
94protected:
95 void OnSpin(wxSpinEvent& event)
96 {
97 m_nb->PerformAction(wxACTION_NOTEBOOK_GOTO, event.GetPosition());
98 }
99
100private:
101 wxNotebook *m_nb;
102
103 DECLARE_EVENT_TABLE()
104};
105
106BEGIN_EVENT_TABLE(wxNotebookSpinBtn, wxSpinButton)
a290fa5a 107 EVT_SPIN(wxID_ANY, wxNotebookSpinBtn::OnSpin)
1e6feb95
VZ
108END_EVENT_TABLE()
109
110// ============================================================================
111// implementation
112// ============================================================================
113
8dba4c72 114IMPLEMENT_DYNAMIC_CLASS(wxNotebook, wxBookCtrlBase)
1e6feb95
VZ
115
116// ----------------------------------------------------------------------------
117// wxNotebook creation
118// ----------------------------------------------------------------------------
119
120void 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
136bool wxNotebook::Create(wxWindow *parent,
137 wxWindowID id,
138 const wxPoint& pos,
139 const wxSize& size,
140 long style,
141 const wxString& name)
142{
90f9b8ef
JS
143 if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
144 style |= wxBK_TOP;
145
1e6feb95
VZ
146 if ( !wxControl::Create(parent, id, pos, size, style,
147 wxDefaultValidator, name) )
a290fa5a 148 return false;
1e6feb95
VZ
149
150 m_sizePad = GetRenderer()->GetTabPadding();
151
170acdc9 152 SetInitialSize(size);
1e6feb95
VZ
153
154 CreateInputHandler(wxINP_HANDLER_NOTEBOOK);
155
a290fa5a 156 return true;
1e6feb95
VZ
157}
158
159// ----------------------------------------------------------------------------
160// wxNotebook page titles and images
161// ----------------------------------------------------------------------------
162
15aad3b9 163wxString wxNotebook::GetPageText(size_t nPage) const
1e6feb95 164{
0966aee3 165 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxEmptyString, _T("invalid notebook page") );
1e6feb95
VZ
166
167 return m_titles[nPage];
168}
169
15aad3b9 170bool wxNotebook::SetPageText(size_t nPage, const wxString& strText)
1e6feb95 171{
a290fa5a 172 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, _T("invalid notebook page") );
1e6feb95
VZ
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
a290fa5a 190 return true;
1e6feb95
VZ
191}
192
15aad3b9 193int wxNotebook::GetPageImage(size_t nPage) const
1e6feb95 194{
90c0f5a9 195 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, _T("invalid notebook page") );
1e6feb95
VZ
196
197 return m_images[nPage];
198}
199
15aad3b9 200bool wxNotebook::SetPageImage(size_t nPage, int nImage)
1e6feb95 201{
a290fa5a 202 wxCHECK_MSG( IS_VALID_PAGE(nPage), false, _T("invalid notebook page") );
1e6feb95 203
a290fa5a 204 wxCHECK_MSG( m_imageList && nImage < m_imageList->GetImageCount(), false,
1e6feb95
VZ
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
a290fa5a 221 return true;
1e6feb95
VZ
222}
223
224wxNotebook::~wxNotebook()
225{
226}
227
228// ----------------------------------------------------------------------------
229// wxNotebook page switching
230// ----------------------------------------------------------------------------
231
76fa43ec 232int wxNotebook::DoSetSelection(size_t nPage, int flags)
1e6feb95 233{
90c0f5a9 234 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, _T("invalid notebook page") );
1e6feb95
VZ
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
a570e8f8 242 if ( flags & SetSelection_SendEvent )
01c6f372 243 {
a570e8f8 244 if ( !SendPageChangingEvent(nPage) )
1d6fcbcc
VZ
245 {
246 // program doesn't allow the page change
247 return m_sel;
248 }
01c6f372
VZ
249 }
250
1d5c2a8e
VZ
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 )
1e6feb95 260 {
a290fa5a 261 RefreshTab(selOld, true /* this tab was selected */);
1e6feb95 262
1d5c2a8e 263 m_pages[selOld]->Hide();
1e6feb95
VZ
264 }
265
1e6feb95
VZ
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
a570e8f8 294 if ( flags & SetSelection_SendEvent )
1d6fcbcc
VZ
295 {
296 // event handling
a570e8f8 297 SendPageChangedEvent(selOld);
1d6fcbcc 298 }
01c6f372
VZ
299
300 return selOld;
1e6feb95
VZ
301}
302
303// ----------------------------------------------------------------------------
304// wxNotebook pages adding/deleting
305// ----------------------------------------------------------------------------
306
15aad3b9 307bool wxNotebook::InsertPage(size_t nPage,
1e6feb95
VZ
308 wxNotebookPage *pPage,
309 const wxString& strText,
310 bool bSelect,
311 int imageId)
312{
15aad3b9 313 size_t nPages = GetPageCount();
a290fa5a 314 wxCHECK_MSG( nPage == nPages || IS_VALID_PAGE(nPage), false,
1e6feb95
VZ
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
a290fa5a 346 bSelect = true;
1e6feb95
VZ
347
348 Relayout();
2b5f62a0 349 Refresh();
1e6feb95
VZ
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
a290fa5a 365 return true;
1e6feb95
VZ
366}
367
368bool wxNotebook::DeleteAllPages()
369{
370 if ( !wxNotebookBase::DeleteAllPages() )
a290fa5a 371 return false;
1e6feb95
VZ
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
a290fa5a 387 return true;
1e6feb95
VZ
388}
389
15aad3b9 390wxNotebookPage *wxNotebook::DoRemovePage(size_t nPage)
1e6feb95
VZ
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
da1158bb
JS
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() )
1e6feb95
VZ
407 {
408 UpdateSpinBtn();
409 }
410
34a0c9f4 411 size_t count = GetPageCount();
1e6feb95
VZ
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
443void wxNotebook::RefreshCurrent()
444{
445 if ( m_sel != INVALID_PAGE )
446 {
447 RefreshTab(m_sel);
448 }
449}
450
1d5c2a8e 451void wxNotebook::RefreshTab(int page, bool forceSelected)
1e6feb95
VZ
452{
453 wxCHECK_RET( IS_VALID_PAGE(page), _T("invalid notebook page") );
454
455 wxRect rect = GetTabRect(page);
1d5c2a8e 456 if ( forceSelected || ((size_t)page == m_sel) )
1e6feb95
VZ
457 {
458 const wxSize indent = GetRenderer()->GetTabIndent();
459 rect.Inflate(indent.x, indent.y);
460 }
461
462 RefreshRect(rect);
463}
464
465void 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
475void 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
c482b99e
JS
482 // Not needed now that wxGenericImageList is being
483 // used for wxUniversal under MSW
484#if 0 // def __WXMSW__ // FIXME
1e6feb95
VZ
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));
a290fa5a 491 m_imageList->Draw(image, dc, 0, 0, wxIMAGELIST_DRAW_NORMAL, true);
62e1ba75 492 dc.SelectObject(wxNullBitmap);
1e6feb95 493#else
49bf4e3e 494 bmp = m_imageList->GetBitmap(image);
1e6feb95
VZ
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
519void 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() )
2b5f62a0 538 {
1e6feb95 539 rectTabs.height -= sizeSpinBtn.y;
2b5f62a0
VZ
540
541 // Allow for erasing the line under selected tab
542 rectTabs.width += 2;
543 }
1e6feb95 544 else
2b5f62a0 545 {
1e6feb95
VZ
546 rectTabs.width -= sizeSpinBtn.x;
547
2b5f62a0
VZ
548 // Allow for erasing the line under selected tab
549 rectTabs.height += 2;
550 }
551
1e6feb95
VZ
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 }
2b5f62a0
VZ
596
597 dc.DestroyClippingRegion();
1e6feb95
VZ
598}
599
600// ----------------------------------------------------------------------------
601// wxNotebook geometry
602// ----------------------------------------------------------------------------
603
e450aa69 604int wxNotebook::HitTest(const wxPoint& pt, long *flags) const
1e6feb95 605{
e450aa69 606 if ( flags )
9804d540 607 *flags = wxBK_HITTEST_NOWHERE;
e450aa69 608
1e6feb95 609 // first check that it is in this window at all
22a35096 610 if ( !GetClientRect().Contains(pt) )
1e6feb95
VZ
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
22a35096 648 if ( rectTabs.Contains(pt) )
e450aa69
VZ
649 {
650 if ( flags )
651 {
652 // TODO: be more precise
9804d540 653 *flags = wxBK_HITTEST_ONITEM;
e450aa69
VZ
654 }
655
1e6feb95 656 return n;
e450aa69 657 }
1e6feb95
VZ
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
669bool wxNotebook::IsVertical() const
670{
671 wxDirection dir = GetTabOrientation();
672
673 return dir == wxLEFT || dir == wxRIGHT;
674}
675
676wxDirection wxNotebook::GetTabOrientation() const
677{
678 long style = GetWindowStyle();
90c0f5a9 679 if ( style & wxBK_BOTTOM )
1e6feb95 680 return wxBOTTOM;
90c0f5a9 681 else if ( style & wxBK_RIGHT )
1e6feb95 682 return wxRIGHT;
90c0f5a9 683 else if ( style & wxBK_LEFT )
1e6feb95
VZ
684 return wxLEFT;
685
90c0f5a9 686 // wxBK_TOP == 0 so we don't have to test for it
1e6feb95
VZ
687 return wxTOP;
688}
689
690wxRect 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
728wxRect 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
757wxRect wxNotebook::GetTabsPart() const
758{
759 wxRect rect = GetAllTabsRect();
760
761 wxDirection dir = GetTabOrientation();
762
763 const wxSize indent = GetRenderer()->GetTabIndent();
764 if ( IsVertical() )
765 {
1e6feb95 766 rect.y += indent.x;
00e086a7
WS
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 }
1e6feb95
VZ
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
794void 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
813void 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
829wxSize 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
857void wxNotebook::ResizeTab(int page)
858{
859 wxSize sizeTab = CalcTabSize(page);
860
861 // we only need full relayout if the page size changes
a290fa5a 862 bool needsRelayout = false;
1e6feb95
VZ
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 {
a290fa5a 874 needsRelayout = true;
1e6feb95
VZ
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
893void wxNotebook::SetPadding(const wxSize& padding)
894{
895 if ( padding != m_sizePad )
896 {
897 m_sizePad = padding;
898
899 Relayout();
900 }
901}
902
903void 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
943wxRect 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
969wxRect 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
983wxSize 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
995void wxNotebook::SetPageSize(const wxSize& size)
996{
997 SetClientSize(GetSizeForPage(size));
998}
999
2ce7af35 1000wxSize wxNotebook::CalcSizeFromPage(const wxSize& sizePage) const
1e6feb95
VZ
1001{
1002 return AdjustSize(GetSizeForPage(sizePage));
1003}
1004
1005// ----------------------------------------------------------------------------
1006// wxNotebook spin button
1007// ----------------------------------------------------------------------------
1008
1009bool wxNotebook::HasSpinBtn() const
1010{
1011 return m_spinbtn && m_spinbtn->IsShown();
1012}
1013
1014void 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
1090void 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
a290fa5a 1100 allTabsShown = true;
1e6feb95
VZ
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 {
b039ef1d 1156 if ( m_spinbtn && m_spinbtn -> IsShown() )
1e6feb95
VZ
1157 {
1158 m_spinbtn->Hide();
1159 }
1160 }
1161}
1162
1163void 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
1208void 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
1226void 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
1271wxSize wxNotebook::DoGetBestClientSize() const
1272{
1273 // calculate the max page size
8b5d5223 1274 wxSize size;
1e6feb95
VZ
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
1299void 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
1307void wxNotebook::DoSetSize(int x, int y,
1308 int width, int height,
1309 int sizeFlags)
1310{
2d4b5a5e 1311 wxSize old_client_size = GetClientSize();
1e6feb95 1312
2d4b5a5e 1313 wxControl::DoSetSize(x, y, width, height, sizeFlags);
32b13913 1314
2d4b5a5e 1315 wxSize new_client_size = GetClientSize();
32b13913 1316
2d4b5a5e
RR
1317 if (old_client_size != new_client_size)
1318 Relayout();
1e6feb95
VZ
1319}
1320
1321// ----------------------------------------------------------------------------
1322// wxNotebook input processing
1323// ----------------------------------------------------------------------------
1324
1325bool wxNotebook::PerformAction(const wxControlAction& action,
1326 long numArg,
1327 const wxString& strArg)
1328{
1329 if ( action == wxACTION_NOTEBOOK_NEXT )
a290fa5a 1330 SetSelection(GetNextPage(true));
1e6feb95 1331 else if ( action == wxACTION_NOTEBOOK_PREV )
a290fa5a 1332 SetSelection(GetNextPage(false));
1e6feb95 1333 else if ( action == wxACTION_NOTEBOOK_GOTO )
01c6f372 1334 SetSelection((int)numArg);
1e6feb95
VZ
1335 else
1336 return wxControl::PerformAction(action, numArg, strArg);
1337
a290fa5a 1338 return true;
1e6feb95
VZ
1339}
1340
9467bdb7
VZ
1341/* static */
1342wxInputHandler *wxNotebook::GetStdInputHandler(wxInputHandler *handlerDef)
1343{
1344 static wxStdNotebookInputHandler s_handler(handlerDef);
1345
1346 return &s_handler;
1347}
1348
1e6feb95
VZ
1349// ----------------------------------------------------------------------------
1350// wxStdNotebookInputHandler
1351// ----------------------------------------------------------------------------
1352
1353wxStdNotebookInputHandler::wxStdNotebookInputHandler(wxInputHandler *inphand)
1354 : wxStdInputHandler(inphand)
1355{
1356}
1357
23645bfa 1358bool wxStdNotebookInputHandler::HandleKey(wxInputConsumer *consumer,
1e6feb95
VZ
1359 const wxKeyEvent& event,
1360 bool pressed)
1361{
1362 // ignore the key releases
1363 if ( pressed )
1364 {
23645bfa 1365 wxNotebook *notebook = wxStaticCast(consumer->GetInputWindow(), wxNotebook);
1e6feb95
VZ
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
a290fa5a 1402 if ( !action.IsEmpty() )
1e6feb95 1403 {
23645bfa 1404 return consumer->PerformAction(action, page);
1e6feb95
VZ
1405 }
1406 }
1407
23645bfa 1408 return wxStdInputHandler::HandleKey(consumer, event, pressed);
1e6feb95
VZ
1409}
1410
23645bfa 1411bool wxStdNotebookInputHandler::HandleMouse(wxInputConsumer *consumer,
1e6feb95
VZ
1412 const wxMouseEvent& event)
1413{
1414 if ( event.ButtonDown(1) )
1415 {
23645bfa 1416 wxNotebook *notebook = wxStaticCast(consumer->GetInputWindow(), wxNotebook);
1e6feb95
VZ
1417 int page = notebook->HitTest(event.GetPosition());
1418 if ( page != -1 )
1419 {
23645bfa 1420 consumer->PerformAction(wxACTION_NOTEBOOK_GOTO, page);
1e6feb95 1421
a290fa5a 1422 return false;
1e6feb95
VZ
1423 }
1424 }
1425
23645bfa 1426 return wxStdInputHandler::HandleMouse(consumer, event);
1e6feb95
VZ
1427}
1428
23645bfa 1429bool wxStdNotebookInputHandler::HandleMouseMove(wxInputConsumer *consumer,
1e6feb95
VZ
1430 const wxMouseEvent& event)
1431{
23645bfa 1432 return wxStdInputHandler::HandleMouseMove(consumer, event);
1e6feb95
VZ
1433}
1434
61fef19b
VZ
1435bool
1436wxStdNotebookInputHandler::HandleFocus(wxInputConsumer *consumer,
1437 const wxFocusEvent& WXUNUSED(event))
1e6feb95 1438{
23645bfa 1439 HandleFocusChange(consumer);
1e6feb95 1440
a290fa5a 1441 return false;
1e6feb95
VZ
1442}
1443
23645bfa 1444bool wxStdNotebookInputHandler::HandleActivation(wxInputConsumer *consumer,
1e6feb95
VZ
1445 bool WXUNUSED(activated))
1446{
1447 // we react to the focus change in the same way as to the [de]activation
23645bfa 1448 HandleFocusChange(consumer);
1e6feb95 1449
a290fa5a 1450 return false;
1e6feb95
VZ
1451}
1452
23645bfa 1453void wxStdNotebookInputHandler::HandleFocusChange(wxInputConsumer *consumer)
1e6feb95 1454{
23645bfa 1455 wxNotebook *notebook = wxStaticCast(consumer->GetInputWindow(), wxNotebook);
1e6feb95
VZ
1456 notebook->RefreshCurrent();
1457}
1458
1459#endif // wxUSE_NOTEBOOK