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