]> git.saurik.com Git - wxWidgets.git/blob - src/msw/notebook.cpp
More small fixes
[wxWidgets.git] / src / msw / notebook.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: msw/notebook.cpp
3 // Purpose: implementation of wxNotebook
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 11.06.98
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #ifdef __GNUG__
13 #pragma implementation "notebook.h"
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #if wxUSE_NOTEBOOK
24
25 // wxWindows
26 #ifndef WX_PRECOMP
27 #include "wx/string.h"
28 #endif // WX_PRECOMP
29
30 #include "wx/log.h"
31 #include "wx/imaglist.h"
32 #include "wx/event.h"
33 #include "wx/control.h"
34 #include "wx/notebook.h"
35
36 #include "wx/msw/private.h"
37
38 // Windows standard headers
39 #ifndef __WIN95__
40 #error "wxNotebook is only supported Windows 95 and above"
41 #endif //Win95
42
43 #include <windowsx.h> // for SetWindowFont
44
45 #ifndef __TWIN32__
46 #ifdef __GNUWIN32_OLD__
47 #include "wx/msw/gnuwin32/extra.h"
48 #endif
49 #endif
50
51 #if defined(__WIN95__) && !((defined(__GNUWIN32_OLD__) || defined(__TWIN32__)) && !defined(__CYGWIN10__))
52 #include <commctrl.h>
53 #endif
54
55 // ----------------------------------------------------------------------------
56 // macros
57 // ----------------------------------------------------------------------------
58
59 // check that the page index is valid
60 #define IS_VALID_PAGE(nPage) (((nPage) >= 0) && ((nPage) < GetPageCount()))
61
62 // hide the ugly cast
63 #define m_hwnd (HWND)GetHWND()
64
65 // ----------------------------------------------------------------------------
66 // constants
67 // ----------------------------------------------------------------------------
68
69 // This is a work-around for missing defines in gcc-2.95 headers
70 #ifndef TCS_RIGHT
71 #define TCS_RIGHT 0x0002
72 #endif
73
74 #ifndef TCS_VERTICAL
75 #define TCS_VERTICAL 0x0080
76 #endif
77
78 #ifndef TCS_BOTTOM
79 #define TCS_BOTTOM TCS_RIGHT
80 #endif
81
82 // ----------------------------------------------------------------------------
83 // event table
84 // ----------------------------------------------------------------------------
85
86 DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED)
87 DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING)
88
89 BEGIN_EVENT_TABLE(wxNotebook, wxControl)
90 EVT_NOTEBOOK_PAGE_CHANGED(-1, wxNotebook::OnSelChange)
91
92 EVT_SIZE(wxNotebook::OnSize)
93
94 EVT_SET_FOCUS(wxNotebook::OnSetFocus)
95
96 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
97 END_EVENT_TABLE()
98
99 IMPLEMENT_DYNAMIC_CLASS(wxNotebook, wxControl)
100 IMPLEMENT_DYNAMIC_CLASS(wxNotebookEvent, wxNotifyEvent)
101
102 // ============================================================================
103 // implementation
104 // ============================================================================
105
106 // ----------------------------------------------------------------------------
107 // wxNotebook construction
108 // ----------------------------------------------------------------------------
109
110 // common part of all ctors
111 void wxNotebook::Init()
112 {
113 m_bOwnsImageList = FALSE;
114 m_imageList = NULL;
115 m_nSelection = -1;
116 }
117
118 // default for dynamic class
119 wxNotebook::wxNotebook()
120 {
121 Init();
122 }
123
124 // the same arguments as for wxControl
125 wxNotebook::wxNotebook(wxWindow *parent,
126 wxWindowID id,
127 const wxPoint& pos,
128 const wxSize& size,
129 long style,
130 const wxString& name)
131 {
132 Init();
133
134 Create(parent, id, pos, size, style, name);
135 }
136
137 // Create() function
138 bool wxNotebook::Create(wxWindow *parent,
139 wxWindowID id,
140 const wxPoint& pos,
141 const wxSize& size,
142 long style,
143 const wxString& name)
144 {
145 // base init
146 if ( !CreateBase(parent, id, pos, size, style, wxDefaultValidator, name) )
147 return FALSE;
148
149 // colors and font
150 m_backgroundColour = wxColour(GetSysColor(COLOR_BTNFACE));
151 m_foregroundColour = *wxBLACK;
152
153 // style
154 m_windowStyle = style | wxTAB_TRAVERSAL;
155
156 long tabStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | TCS_TABS;
157
158 if ( m_windowStyle & wxCLIP_SIBLINGS )
159 tabStyle |= WS_CLIPSIBLINGS;
160 if (m_windowStyle & wxCLIP_CHILDREN)
161 tabStyle |= WS_CLIPCHILDREN;
162 if ( m_windowStyle & wxTC_MULTILINE )
163 tabStyle |= TCS_MULTILINE;
164 if ( m_windowStyle & wxBORDER )
165 tabStyle &= WS_BORDER;
166 if (m_windowStyle & wxNB_FIXEDWIDTH)
167 tabStyle |= TCS_FIXEDWIDTH ;
168 if (m_windowStyle & wxNB_BOTTOM)
169 tabStyle |= TCS_RIGHT;
170 if (m_windowStyle & wxNB_LEFT)
171 tabStyle |= TCS_VERTICAL;
172 if (m_windowStyle & wxNB_RIGHT)
173 tabStyle |= TCS_VERTICAL|TCS_RIGHT;
174
175
176 if ( !MSWCreate(GetId(), GetParent(), WC_TABCONTROL,
177 this, NULL, pos.x, pos.y, size.x, size.y,
178 tabStyle, NULL, 0) )
179 {
180 return FALSE;
181 }
182
183 // Not all compilers recognise SetWindowFont
184 ::SendMessage(GetHwnd(), WM_SETFONT,
185 (WPARAM)::GetStockObject(DEFAULT_GUI_FONT), TRUE);
186
187
188 if ( parent != NULL )
189 parent->AddChild(this);
190
191 SubclassWin(m_hWnd);
192
193 return TRUE;
194 }
195
196 // dtor
197 wxNotebook::~wxNotebook()
198 {
199 if (m_bOwnsImageList)
200 delete m_imageList;
201 }
202
203 // ----------------------------------------------------------------------------
204 // wxNotebook accessors
205 // ----------------------------------------------------------------------------
206 int wxNotebook::GetPageCount() const
207 {
208 // consistency check
209 wxASSERT( (int)m_pages.Count() == TabCtrl_GetItemCount(m_hwnd) );
210
211 return m_pages.Count();
212 }
213
214 int wxNotebook::GetRowCount() const
215 {
216 return TabCtrl_GetRowCount(m_hwnd);
217 }
218
219 int wxNotebook::SetSelection(int nPage)
220 {
221 wxCHECK_MSG( IS_VALID_PAGE(nPage), -1, wxT("notebook page out of range") );
222
223 ChangePage(m_nSelection, nPage);
224
225 return TabCtrl_SetCurSel(m_hwnd, nPage);
226 }
227
228 bool wxNotebook::SetPageText(int nPage, const wxString& strText)
229 {
230 wxCHECK_MSG( IS_VALID_PAGE(nPage), FALSE, wxT("notebook page out of range") );
231
232 TC_ITEM tcItem;
233 tcItem.mask = TCIF_TEXT;
234 tcItem.pszText = (wxChar *)strText.c_str();
235
236 return TabCtrl_SetItem(m_hwnd, nPage, &tcItem) != 0;
237 }
238
239 wxString wxNotebook::GetPageText(int nPage) const
240 {
241 wxCHECK_MSG( IS_VALID_PAGE(nPage), wxT(""), wxT("notebook page out of range") );
242
243 wxChar buf[256];
244 TC_ITEM tcItem;
245 tcItem.mask = TCIF_TEXT;
246 tcItem.pszText = buf;
247 tcItem.cchTextMax = WXSIZEOF(buf);
248
249 wxString str;
250 if ( TabCtrl_GetItem(m_hwnd, nPage, &tcItem) )
251 str = tcItem.pszText;
252
253 return str;
254 }
255
256 int wxNotebook::GetPageImage(int nPage) const
257 {
258 wxCHECK_MSG( IS_VALID_PAGE(nPage), -1, wxT("notebook page out of range") );
259
260 TC_ITEM tcItem;
261 tcItem.mask = TCIF_IMAGE;
262
263 return TabCtrl_GetItem(m_hwnd, nPage, &tcItem) ? tcItem.iImage : -1;
264 }
265
266 bool wxNotebook::SetPageImage(int nPage, int nImage)
267 {
268 wxCHECK_MSG( IS_VALID_PAGE(nPage), FALSE, wxT("notebook page out of range") );
269
270 TC_ITEM tcItem;
271 tcItem.mask = TCIF_IMAGE;
272 tcItem.iImage = nImage;
273
274 return TabCtrl_SetItem(m_hwnd, nPage, &tcItem) != 0;
275 }
276
277 void wxNotebook::SetImageList(wxImageList* imageList)
278 {
279 if ( m_bOwnsImageList )
280 {
281 delete m_imageList;
282 }
283
284 m_bOwnsImageList = FALSE;
285 m_imageList = imageList;
286
287 TabCtrl_SetImageList(m_hwnd, (HIMAGELIST)imageList->GetHIMAGELIST());
288 }
289
290 void wxNotebook::AssignImageList(wxImageList* imageList)
291 {
292 SetImageList(imageList);
293 m_bOwnsImageList = TRUE;
294 }
295
296 // ----------------------------------------------------------------------------
297 // wxNotebook size settings
298 // ----------------------------------------------------------------------------
299
300 void wxNotebook::SetPageSize(const wxSize& size)
301 {
302 // transform the page size into the notebook size
303 RECT rc;
304 rc.left =
305 rc.top = 0;
306 rc.right = size.x;
307 rc.bottom = size.y;
308
309 TabCtrl_AdjustRect(GetHwnd(), TRUE, &rc);
310
311 // and now set it
312 SetSize(rc.right - rc.left, rc.bottom - rc.top);
313 }
314
315 void wxNotebook::SetPadding(const wxSize& padding)
316 {
317 TabCtrl_SetPadding(GetHwnd(), padding.x, padding.y);
318 }
319
320 // Windows-only at present. Also, you must use the wxNB_FIXEDWIDTH
321 // style.
322 void wxNotebook::SetTabSize(const wxSize& sz)
323 {
324 ::SendMessage(GetHwnd(), TCM_SETITEMSIZE, 0, MAKELPARAM(sz.x, sz.y));
325 }
326
327 // ----------------------------------------------------------------------------
328 // wxNotebook operations
329 // ----------------------------------------------------------------------------
330
331 // remove one page from the notebook
332 bool wxNotebook::DeletePage(int nPage)
333 {
334 wxCHECK_MSG( IS_VALID_PAGE(nPage), FALSE, wxT("notebook page out of range") );
335
336 if ( m_nSelection == nPage ) {
337 // advance selection backwards - the page being deleted shouldn't be left
338 // selected
339 AdvanceSelection(FALSE);
340 }
341
342 TabCtrl_DeleteItem(m_hwnd, nPage);
343
344 delete m_pages[nPage];
345 m_pages.Remove(nPage);
346
347 if ( m_pages.IsEmpty() ) {
348 // no selection if the notebook became empty
349 m_nSelection = -1;
350 }
351 else
352 m_nSelection = TabCtrl_GetCurSel(m_hwnd);
353
354
355 return TRUE;
356 }
357
358 // remove one page from the notebook, without deleting
359 wxNotebookPage *wxNotebook::DoRemovePage(int nPage)
360 {
361 wxCHECK_MSG( IS_VALID_PAGE(nPage), NULL, wxT("notebook page out of range") );
362
363 TabCtrl_DeleteItem(m_hwnd, nPage);
364
365 wxNotebookPage *pageRemoved = m_pages[nPage];
366 m_pages.Remove(nPage);
367
368 if ( m_pages.IsEmpty() )
369 m_nSelection = -1;
370 else
371 m_nSelection = TabCtrl_GetCurSel(m_hwnd);
372
373 return pageRemoved;
374 }
375
376 // remove all pages
377 bool wxNotebook::DeleteAllPages()
378 {
379 int nPageCount = GetPageCount();
380 int nPage;
381 for ( nPage = 0; nPage < nPageCount; nPage++ )
382 delete m_pages[nPage];
383
384 m_pages.Clear();
385
386 TabCtrl_DeleteAllItems(m_hwnd);
387
388 m_nSelection = -1;
389
390 return TRUE;
391 }
392
393 // add a page to the notebook
394 bool wxNotebook::AddPage(wxNotebookPage *pPage,
395 const wxString& strText,
396 bool bSelect,
397 int imageId)
398 {
399 return InsertPage(GetPageCount(), pPage, strText, bSelect, imageId);
400 }
401
402 // same as AddPage() but does it at given position
403 bool wxNotebook::InsertPage(int nPage,
404 wxNotebookPage *pPage,
405 const wxString& strText,
406 bool bSelect,
407 int imageId)
408 {
409 wxASSERT( pPage != NULL );
410 wxCHECK( IS_VALID_PAGE(nPage) || nPage == GetPageCount(), FALSE );
411
412 // do add the tab to the control
413
414 // init all fields to 0
415 TC_ITEM tcItem;
416 memset(&tcItem, 0, sizeof(tcItem));
417
418 if ( imageId != -1 )
419 {
420 tcItem.mask |= TCIF_IMAGE;
421 tcItem.iImage = imageId;
422 }
423
424 if ( !strText.IsEmpty() )
425 {
426 tcItem.mask |= TCIF_TEXT;
427 tcItem.pszText = (wxChar *)strText.c_str(); // const_cast
428 }
429
430 if ( TabCtrl_InsertItem(m_hwnd, nPage, &tcItem) == -1 ) {
431 wxLogError(wxT("Can't create the notebook page '%s'."), strText.c_str());
432
433 return FALSE;
434 }
435
436 // if the inserted page is before the selected one, we must update the
437 // index of the selected page
438 if ( nPage <= m_nSelection )
439 {
440 // one extra page added
441 m_nSelection++;
442 }
443
444 // save the pointer to the page
445 m_pages.Insert(pPage, nPage);
446
447 // don't show pages by default (we'll need to adjust their size first)
448 HWND hwnd = GetWinHwnd(pPage);
449 SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_VISIBLE);
450
451 // this updates internal flag too - otherwise it will get out of sync
452 pPage->Show(FALSE);
453
454 // fit the notebook page to the tab control's display area
455 RECT rc;
456 rc.left = rc.top = 0;
457 GetSize((int *)&rc.right, (int *)&rc.bottom);
458 TabCtrl_AdjustRect(m_hwnd, FALSE, &rc);
459 pPage->SetSize(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
460
461 // some page should be selected: either this one or the first one if there is
462 // still no selection
463 int selNew = -1;
464 if ( bSelect )
465 selNew = nPage;
466 else if ( m_nSelection == -1 )
467 selNew = 0;
468
469 if ( selNew != -1 )
470 SetSelection(selNew);
471
472 return TRUE;
473 }
474
475 // ----------------------------------------------------------------------------
476 // wxNotebook callbacks
477 // ----------------------------------------------------------------------------
478
479 void wxNotebook::OnSize(wxSizeEvent& event)
480 {
481 // fit the notebook page to the tab control's display area
482 RECT rc;
483 rc.left = rc.top = 0;
484 GetSize((int *)&rc.right, (int *)&rc.bottom);
485
486 TabCtrl_AdjustRect(m_hwnd, FALSE, &rc);
487
488 int width = rc.right - rc.left,
489 height = rc.bottom - rc.top;
490 size_t nCount = m_pages.Count();
491 for ( size_t nPage = 0; nPage < nCount; nPage++ ) {
492 wxNotebookPage *pPage = m_pages[nPage];
493 pPage->SetSize(rc.left, rc.top, width, height);
494 }
495
496 event.Skip();
497 }
498
499 void wxNotebook::OnSelChange(wxNotebookEvent& event)
500 {
501 // is it our tab control?
502 if ( event.GetEventObject() == this )
503 {
504 int sel = event.GetOldSelection();
505 if ( sel != -1 )
506 m_pages[sel]->Show(FALSE);
507
508 sel = event.GetSelection();
509 if ( sel != -1 )
510 {
511 wxNotebookPage *pPage = m_pages[sel];
512 pPage->Show(TRUE);
513 pPage->SetFocus();
514 }
515
516 m_nSelection = sel;
517 }
518
519 // we want to give others a chance to process this message as well
520 event.Skip();
521 }
522
523 void wxNotebook::OnSetFocus(wxFocusEvent& event)
524 {
525 // this function is only called when the focus is explicitly set (i.e. from
526 // the program) to the notebook - in this case we don't need the
527 // complicated OnNavigationKey() logic because the programmer knows better
528 // what [s]he wants
529
530 // set focus to the currently selected page if any
531 if ( m_nSelection != -1 )
532 m_pages[m_nSelection]->SetFocus();
533
534 event.Skip();
535 }
536
537 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
538 {
539 if ( event.IsWindowChange() ) {
540 // change pages
541 AdvanceSelection(event.GetDirection());
542 }
543 else {
544 // we get this event in 2 cases
545 //
546 // a) one of our pages might have generated it because the user TABbed
547 // out from it in which case we should propagate the event upwards and
548 // our parent will take care of setting the focus to prev/next sibling
549 //
550 // or
551 //
552 // b) the parent panel wants to give the focus to us so that we
553 // forward it to our selected page. We can't deal with this in
554 // OnSetFocus() because we don't know which direction the focus came
555 // from in this case and so can't choose between setting the focus to
556 // first or last panel child
557
558 wxWindow *parent = GetParent();
559 if ( event.GetEventObject() == parent )
560 {
561 // no, it doesn't come from child, case (b): forward to a page
562 if ( m_nSelection != -1 )
563 {
564 // so that the page knows that the event comes from it's parent
565 // and is being propagated downwards
566 event.SetEventObject(this);
567
568 wxWindow *page = m_pages[m_nSelection];
569 if ( !page->GetEventHandler()->ProcessEvent(event) )
570 {
571 page->SetFocus();
572 }
573 //else: page manages focus inside it itself
574 }
575 else
576 {
577 // we have no pages - still have to give focus to _something_
578 SetFocus();
579 }
580 }
581 else
582 {
583 // it comes from our child, case (a), pass to the parent
584 if ( parent ) {
585 event.SetCurrentFocus(this);
586 parent->GetEventHandler()->ProcessEvent(event);
587 }
588 }
589 }
590 }
591
592 // ----------------------------------------------------------------------------
593 // wxNotebook base class virtuals
594 // ----------------------------------------------------------------------------
595
596 // override these 2 functions to do nothing: everything is done in OnSize
597
598 void wxNotebook::SetConstraintSizes(bool WXUNUSED(recurse))
599 {
600 // don't set the sizes of the pages - their correct size is not yet known
601 wxControl::SetConstraintSizes(FALSE);
602 }
603
604 bool wxNotebook::DoPhase(int WXUNUSED(nPhase))
605 {
606 return TRUE;
607 }
608
609 bool wxNotebook::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM* result)
610 {
611 wxNotebookEvent event(wxEVT_NULL, m_windowId);
612
613 NMHDR* hdr = (NMHDR *)lParam;
614 switch ( hdr->code ) {
615 case TCN_SELCHANGE:
616 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
617 break;
618
619 case TCN_SELCHANGING:
620 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING);
621 break;
622
623 default:
624 return wxControl::MSWOnNotify(idCtrl, lParam, result);
625 }
626
627 event.SetSelection(TabCtrl_GetCurSel(m_hwnd));
628 event.SetOldSelection(m_nSelection);
629 event.SetEventObject(this);
630 event.SetInt(idCtrl);
631
632 bool processed = GetEventHandler()->ProcessEvent(event);
633 *result = !event.IsAllowed();
634 return processed;
635 }
636
637 // ----------------------------------------------------------------------------
638 // wxNotebook helper functions
639 // ----------------------------------------------------------------------------
640
641 // generate the page changing and changed events, hide the currently active
642 // panel and show the new one
643 void wxNotebook::ChangePage(int nOldSel, int nSel)
644 {
645 // MT-FIXME should use a real semaphore
646 static bool s_bInsideChangePage = FALSE;
647
648 // when we call ProcessEvent(), our own OnSelChange() is called which calls
649 // this function - break the infinite loop
650 if ( s_bInsideChangePage )
651 return;
652
653 // it's not an error (the message may be generated by the tab control itself)
654 // and it may happen - just do nothing
655 if ( nSel == nOldSel )
656 return;
657
658 s_bInsideChangePage = TRUE;
659
660 wxNotebookEvent event(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, m_windowId);
661 event.SetSelection(nSel);
662 event.SetOldSelection(nOldSel);
663 event.SetEventObject(this);
664 if ( GetEventHandler()->ProcessEvent(event) && !event.IsAllowed() )
665 {
666 // program doesn't allow the page change
667 s_bInsideChangePage = FALSE;
668 return;
669 }
670
671 event.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
672 GetEventHandler()->ProcessEvent(event);
673
674 s_bInsideChangePage = FALSE;
675 }
676
677 #endif // wxUSE_NOTEBOOK