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