move notebook event definition to common code
[wxWidgets.git] / src / gtk / notebook.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/notebook.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling, Vadim Zeitlin
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #if wxUSE_NOTEBOOK
14
15 #include "wx/notebook.h"
16
17 #ifndef WX_PRECOMP
18 #include "wx/intl.h"
19 #include "wx/log.h"
20 #include "wx/utils.h"
21 #include "wx/msgdlg.h"
22 #include "wx/bitmap.h"
23 #endif
24
25 #include "wx/imaglist.h"
26 #include "wx/fontutil.h"
27
28 #include "wx/gtk/private.h"
29
30 #include <gdk/gdkkeysyms.h>
31
32 //-----------------------------------------------------------------------------
33 // data
34 //-----------------------------------------------------------------------------
35
36 extern bool g_blockEventsOnDrag;
37
38 //-----------------------------------------------------------------------------
39 // wxGtkNotebookPage
40 //-----------------------------------------------------------------------------
41
42 // VZ: this is rather ugly as we keep the pages themselves in an array (it
43 // allows us to have quite a few functions implemented in the base class)
44 // but the page data is kept in a separate list, so we must maintain them
45 // in sync manually... of course, the list had been there before the base
46 // class which explains it but it still would be nice to do something
47 // about this one day
48
49 class wxGtkNotebookPage: public wxObject
50 {
51 public:
52 wxGtkNotebookPage()
53 {
54 m_image = -1;
55 m_box = NULL;
56 }
57
58 wxString m_text;
59 int m_image;
60 GtkLabel *m_label;
61 GtkWidget *m_box; // in which the label and image are packed
62 };
63
64
65 #include "wx/listimpl.cpp"
66 WX_DEFINE_LIST(wxGtkNotebookPagesList)
67
68
69 //-----------------------------------------------------------------------------
70 // "switch_page"
71 //-----------------------------------------------------------------------------
72
73 extern "C" {
74 static void gtk_notebook_page_changing_callback( GtkNotebook *widget,
75 GtkNotebookPage *WXUNUSED(gpage),
76 guint page,
77 wxNotebook *notebook )
78 {
79 int old = gtk_notebook_get_current_page( widget );
80
81 if ( !notebook->SendPageChangingEvent(page) )
82 {
83 // program doesn't allow the page change
84 g_signal_stop_emission_by_name( widget, "switch_page" );
85 }
86 else
87 {
88 // the page change event also reports the old page
89 notebook->m_oldSelection = old;
90 }
91 }
92 }
93
94 extern "C" {
95 static void gtk_notebook_page_changed_callback( GtkNotebook * WXUNUSED(widget),
96 GtkNotebookPage *WXUNUSED(gpage),
97 guint WXUNUSED(page),
98 wxNotebook *notebook )
99 {
100 int old = notebook->m_oldSelection;
101 notebook->SendPageChangedEvent( old );
102 }
103 }
104
105 //-----------------------------------------------------------------------------
106 // InsertChild callback for wxNotebook
107 //-----------------------------------------------------------------------------
108
109 static void wxInsertChildInNotebook(wxWindow* parent, wxWindow* child)
110 {
111 // Hack Alert! (Part I): This sets the notebook as the parent of the child
112 // widget, and takes care of some details such as updating the state and
113 // style of the child to reflect its new location. We do this early
114 // because without it GetBestSize (which is used to set the initial size
115 // of controls if an explicit size is not given) will often report
116 // incorrect sizes since the widget's style context is not fully known.
117 // See bug #901694 for details
118 // (http://sourceforge.net/tracker/?func=detail&aid=901694&group_id=9863&atid=109863)
119 gtk_widget_set_parent(child->m_widget, parent->m_widget);
120
121 // NOTE: This should be considered a temporary workaround until we can
122 // work out the details and implement delaying the setting of the initial
123 // size of widgets until the size is really needed.
124 }
125
126 //-----------------------------------------------------------------------------
127 // wxNotebook
128 //-----------------------------------------------------------------------------
129
130 IMPLEMENT_DYNAMIC_CLASS(wxNotebook,wxBookCtrlBase)
131
132 BEGIN_EVENT_TABLE(wxNotebook, wxBookCtrlBase)
133 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
134 END_EVENT_TABLE()
135
136 void wxNotebook::Init()
137 {
138 m_padding = 0;
139
140 m_imageList = (wxImageList *) NULL;
141 m_oldSelection = -1;
142 m_themeEnabled = true;
143 }
144
145 wxNotebook::wxNotebook()
146 {
147 Init();
148 }
149
150 wxNotebook::wxNotebook( wxWindow *parent, wxWindowID id,
151 const wxPoint& pos, const wxSize& size,
152 long style, const wxString& name )
153 {
154 Init();
155 Create( parent, id, pos, size, style, name );
156 }
157
158 wxNotebook::~wxNotebook()
159 {
160 DeleteAllPages();
161 }
162
163 bool wxNotebook::Create(wxWindow *parent, wxWindowID id,
164 const wxPoint& pos, const wxSize& size,
165 long style, const wxString& name )
166 {
167 m_insertCallback = wxInsertChildInNotebook;
168
169 if ( (style & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
170 style |= wxBK_TOP;
171
172 if (!PreCreation( parent, pos, size ) ||
173 !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name ))
174 {
175 wxFAIL_MSG( wxT("wxNoteBook creation failed") );
176 return false;
177 }
178
179
180 m_widget = gtk_notebook_new();
181
182 gtk_notebook_set_scrollable( GTK_NOTEBOOK(m_widget), 1 );
183
184 g_signal_connect (m_widget, "switch_page",
185 G_CALLBACK (gtk_notebook_page_changing_callback), this);
186
187 g_signal_connect_after (m_widget, "switch_page",
188 G_CALLBACK (gtk_notebook_page_changed_callback), this);
189
190 m_parent->DoAddChild( this );
191
192 if (m_windowStyle & wxBK_RIGHT)
193 gtk_notebook_set_tab_pos( GTK_NOTEBOOK(m_widget), GTK_POS_RIGHT );
194 if (m_windowStyle & wxBK_LEFT)
195 gtk_notebook_set_tab_pos( GTK_NOTEBOOK(m_widget), GTK_POS_LEFT );
196 if (m_windowStyle & wxBK_BOTTOM)
197 gtk_notebook_set_tab_pos( GTK_NOTEBOOK(m_widget), GTK_POS_BOTTOM );
198
199 PostCreation(size);
200
201 return true;
202 }
203
204 int wxNotebook::GetSelection() const
205 {
206 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid notebook") );
207
208 return gtk_notebook_get_current_page( GTK_NOTEBOOK(m_widget) );
209 }
210
211 wxString wxNotebook::GetPageText( size_t page ) const
212 {
213 wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid notebook") );
214
215 wxGtkNotebookPage* nb_page = GetNotebookPage(page);
216 if (nb_page)
217 return nb_page->m_text;
218 else
219 return wxEmptyString;
220 }
221
222 int wxNotebook::GetPageImage( size_t page ) const
223 {
224 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid notebook") );
225
226 wxGtkNotebookPage* nb_page = GetNotebookPage(page);
227 if (nb_page)
228 return nb_page->m_image;
229 else
230 return -1;
231 }
232
233 wxGtkNotebookPage* wxNotebook::GetNotebookPage( int page ) const
234 {
235 wxCHECK_MSG( m_widget != NULL, (wxGtkNotebookPage*) NULL, wxT("invalid notebook") );
236
237 wxCHECK_MSG( page < (int)m_pagesData.GetCount(), (wxGtkNotebookPage*) NULL, wxT("invalid notebook index") );
238
239 return m_pagesData.Item(page)->GetData();
240 }
241
242 int wxNotebook::DoSetSelection( size_t page, int flags )
243 {
244 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid notebook") );
245
246 wxCHECK_MSG( page < m_pagesData.GetCount(), -1, wxT("invalid notebook index") );
247
248 int selOld = GetSelection();
249
250 if ( !(flags & SetSelection_SendEvent) )
251 {
252 g_signal_handlers_block_by_func(m_widget,
253 (gpointer)gtk_notebook_page_changing_callback, this);
254
255 g_signal_handlers_block_by_func(m_widget,
256 (gpointer)gtk_notebook_page_changed_callback, this);
257 }
258
259 gtk_notebook_set_current_page( GTK_NOTEBOOK(m_widget), page );
260
261 if ( !(flags & SetSelection_SendEvent) )
262 {
263 g_signal_handlers_unblock_by_func(m_widget,
264 (gpointer)gtk_notebook_page_changing_callback, this);
265
266 g_signal_handlers_unblock_by_func(m_widget,
267 (gpointer)gtk_notebook_page_changed_callback, this);
268 }
269
270 wxNotebookPage *client = GetPage(page);
271 if ( client )
272 client->SetFocus();
273
274 return selOld;
275 }
276
277 bool wxNotebook::SetPageText( size_t page, const wxString &text )
278 {
279 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid notebook") );
280
281 wxGtkNotebookPage* nb_page = GetNotebookPage(page);
282
283 wxCHECK_MSG( nb_page, false, wxT("SetPageText: invalid page index") );
284
285 nb_page->m_text = text;
286
287 gtk_label_set_text( nb_page->m_label, wxGTK_CONV( nb_page->m_text ) );
288
289 return true;
290 }
291
292 bool wxNotebook::SetPageImage( size_t page, int image )
293 {
294 /* HvdH 28-12-98: now it works, but it's a bit of a kludge */
295
296 wxGtkNotebookPage* nb_page = GetNotebookPage(page);
297
298 if (!nb_page) return false;
299
300 /* Optimization posibility: return immediately if image unchanged.
301 * Not enabled because it may break existing (stupid) code that
302 * manipulates the imagelist to cycle images */
303
304 /* if (image == nb_page->m_image) return true; */
305
306 /* For different cases:
307 1) no image -> no image
308 2) image -> no image
309 3) no image -> image
310 4) image -> image */
311
312 if (image == -1 && nb_page->m_image == -1)
313 return true; /* Case 1): Nothing to do. */
314
315 GtkWidget *pixmapwid = (GtkWidget*) NULL;
316
317 if (nb_page->m_image != -1)
318 {
319 /* Case 2) or 4). There is already an image in the gtkhbox. Let's find it */
320
321 GList *child = gtk_container_get_children(GTK_CONTAINER(nb_page->m_box));
322 while (child)
323 {
324 if (GTK_IS_IMAGE(child->data))
325 {
326 pixmapwid = GTK_WIDGET(child->data);
327 break;
328 }
329 child = child->next;
330 }
331
332 /* We should have the pixmap widget now */
333 wxASSERT(pixmapwid != NULL);
334
335 if (image == -1)
336 {
337 /* If there's no new widget, just remove the old from the box */
338 gtk_container_remove(GTK_CONTAINER(nb_page->m_box), pixmapwid);
339 nb_page->m_image = -1;
340
341 return true; /* Case 2) */
342 }
343 }
344
345 /* Only cases 3) and 4) left */
346 wxASSERT( m_imageList != NULL ); /* Just in case */
347
348 /* Construct the new pixmap */
349 const wxBitmap *bmp = m_imageList->GetBitmapPtr(image);
350
351 if (pixmapwid == NULL)
352 {
353 /* Case 3) No old pixmap. Create a new one and prepend it to the hbox */
354 pixmapwid = gtk_image_new_from_pixbuf(bmp->GetPixbuf());
355
356 /* CHECKME: Are these pack flags okay? */
357 gtk_box_pack_start(GTK_BOX(nb_page->m_box), pixmapwid, FALSE, FALSE, m_padding);
358 gtk_widget_show(pixmapwid);
359 }
360 else
361 {
362 /* Case 4) Simply replace the pixmap */
363 gtk_image_set_from_pixbuf((GtkImage*)pixmapwid, bmp->GetPixbuf());
364 }
365
366 nb_page->m_image = image;
367
368 return true;
369 }
370
371 void wxNotebook::SetPageSize( const wxSize &WXUNUSED(size) )
372 {
373 wxFAIL_MSG( wxT("wxNotebook::SetPageSize not implemented") );
374 }
375
376 void wxNotebook::SetPadding( const wxSize &padding )
377 {
378 wxCHECK_RET( m_widget != NULL, wxT("invalid notebook") );
379
380 m_padding = padding.GetWidth();
381
382 int i;
383 for (i=0; i<int(GetPageCount()); i++)
384 {
385 wxGtkNotebookPage* nb_page = GetNotebookPage(i);
386 wxASSERT(nb_page != NULL);
387
388 if (nb_page->m_image != -1)
389 {
390 // gtk_box_set_child_packing sets padding on BOTH sides
391 // icon provides left padding, label provides center and right
392 int image = nb_page->m_image;
393 SetPageImage(i,-1);
394 SetPageImage(i,image);
395 }
396 wxASSERT(nb_page->m_label);
397 gtk_box_set_child_packing(GTK_BOX(nb_page->m_box),
398 GTK_WIDGET(nb_page->m_label),
399 FALSE, FALSE, m_padding, GTK_PACK_END);
400 }
401 }
402
403 void wxNotebook::SetTabSize(const wxSize& WXUNUSED(sz))
404 {
405 wxFAIL_MSG( wxT("wxNotebook::SetTabSize not implemented") );
406 }
407
408 bool wxNotebook::DeleteAllPages()
409 {
410 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid notebook") );
411
412 while (m_pagesData.GetCount() > 0)
413 DeletePage( m_pagesData.GetCount()-1 );
414
415 wxASSERT_MSG( GetPageCount() == 0, _T("all pages must have been deleted") );
416
417 InvalidateBestSize();
418 return wxNotebookBase::DeleteAllPages();
419 }
420
421 wxNotebookPage *wxNotebook::DoRemovePage( size_t page )
422 {
423 // We cannot remove the page yet, as GTK sends the "switch_page"
424 // signal before it has removed the notebook-page from its
425 // corresponding list. Thus, if we were to remove the page from
426 // m_pages at this point, the two lists of pages would be out
427 // of sync during the PAGE_CHANGING/PAGE_CHANGED events.
428 wxNotebookPage *client = GetPage(page);
429 if ( !client )
430 return NULL;
431
432 gtk_widget_ref( client->m_widget );
433 gtk_widget_unrealize( client->m_widget );
434
435 // we don't need to unparent the client->m_widget; GTK+ will do
436 // that for us (and will throw a warning if we do it!)
437 gtk_notebook_remove_page( GTK_NOTEBOOK(m_widget), page );
438
439 // It's safe to remove the page now.
440 wxASSERT_MSG(GetPage(page) == client, wxT("pages changed during delete"));
441 wxNotebookBase::DoRemovePage(page);
442
443 wxGtkNotebookPage* p = GetNotebookPage(page);
444 m_pagesData.DeleteObject(p);
445 delete p;
446
447 return client;
448 }
449
450 bool wxNotebook::InsertPage( size_t position,
451 wxNotebookPage* win,
452 const wxString& text,
453 bool select,
454 int imageId )
455 {
456 wxCHECK_MSG( m_widget != NULL, false, wxT("invalid notebook") );
457
458 wxCHECK_MSG( win->GetParent() == this, false,
459 wxT("Can't add a page whose parent is not the notebook!") );
460
461 wxCHECK_MSG( position <= GetPageCount(), false,
462 _T("invalid page index in wxNotebookPage::InsertPage()") );
463
464 // Hack Alert! (Part II): See above in wxInsertChildInNotebook callback
465 // why this has to be done. NOTE: using gtk_widget_unparent here does not
466 // work as it seems to undo too much and will cause errors in the
467 // gtk_notebook_insert_page below, so instead just clear the parent by
468 // hand here.
469 win->m_widget->parent = NULL;
470
471 if (m_themeEnabled)
472 win->SetThemeEnabled(true);
473
474 GtkNotebook *notebook = GTK_NOTEBOOK(m_widget);
475
476 wxGtkNotebookPage *nb_page = new wxGtkNotebookPage();
477
478 if ( position == GetPageCount() )
479 m_pagesData.Append( nb_page );
480 else
481 m_pagesData.Insert( position, nb_page );
482
483 m_pages.Insert(win, position);
484
485 // set the label image and text
486 // this must be done before adding the page, as GetPageText
487 // and GetPageImage will otherwise return wrong values in
488 // the page-changed event that results from inserting the
489 // first page.
490 nb_page->m_image = imageId;
491 nb_page->m_text = wxStripMenuCodes(text);
492
493 nb_page->m_box = gtk_hbox_new( FALSE, 1 );
494 gtk_container_set_border_width((GtkContainer*)nb_page->m_box, 2);
495
496 gtk_notebook_insert_page(notebook, win->m_widget, nb_page->m_box, position);
497
498 if (imageId != -1)
499 {
500 wxASSERT( m_imageList != NULL );
501
502 const wxBitmap *bmp = m_imageList->GetBitmapPtr(imageId);
503 GtkWidget* pixmapwid = gtk_image_new_from_pixbuf(bmp->GetPixbuf());
504 gtk_box_pack_start(GTK_BOX(nb_page->m_box), pixmapwid, FALSE, FALSE, m_padding);
505 gtk_widget_show(pixmapwid);
506 }
507
508 /* set the label text */
509 nb_page->m_label = GTK_LABEL( gtk_label_new(wxGTK_CONV(nb_page->m_text)) );
510 gtk_box_pack_end( GTK_BOX(nb_page->m_box), GTK_WIDGET(nb_page->m_label), FALSE, FALSE, m_padding );
511
512 /* apply current style */
513 GtkRcStyle *style = CreateWidgetStyle();
514 if ( style )
515 {
516 gtk_widget_modify_style(GTK_WIDGET(nb_page->m_label), style);
517 gtk_rc_style_unref(style);
518 }
519
520 /* show the label */
521 gtk_widget_show( GTK_WIDGET(nb_page->m_label) );
522
523 if (select && (m_pagesData.GetCount() > 1))
524 {
525 SetSelection( position );
526 }
527
528 InvalidateBestSize();
529 return true;
530 }
531
532 // helper for HitTest(): check if the point lies inside the given widget which
533 // is the child of the notebook whose position and border size are passed as
534 // parameters
535 static bool
536 IsPointInsideWidget(const wxPoint& pt, GtkWidget *w,
537 gint x, gint y, gint border = 0)
538 {
539 return
540 (pt.x >= w->allocation.x - x - border) &&
541 (pt.x <= w->allocation.x - x + border + w->allocation.width) &&
542 (pt.y >= w->allocation.y - y - border) &&
543 (pt.y <= w->allocation.y - y + border + w->allocation.height);
544 }
545
546 int wxNotebook::HitTest(const wxPoint& pt, long *flags) const
547 {
548 const gint x = m_widget->allocation.x;
549 const gint y = m_widget->allocation.y;
550
551 const size_t count = GetPageCount();
552 size_t i = 0;
553
554 GtkNotebook * notebook = GTK_NOTEBOOK(m_widget);
555 if (gtk_notebook_get_scrollable(notebook))
556 i = g_list_position( notebook->children, notebook->first_tab );
557
558 for ( ; i < count; i++ )
559 {
560 wxGtkNotebookPage* nb_page = GetNotebookPage(i);
561 GtkWidget *box = nb_page->m_box;
562
563 const gint border = gtk_container_get_border_width(GTK_CONTAINER(box));
564
565 if ( IsPointInsideWidget(pt, box, x, y, border) )
566 {
567 // ok, we're inside this tab -- now find out where, if needed
568 if ( flags )
569 {
570 GtkWidget *pixmap = NULL;
571
572 GList *children = gtk_container_get_children(GTK_CONTAINER(box));
573 for ( GList *child = children; child; child = child->next )
574 {
575 if (GTK_IS_IMAGE(child->data))
576 {
577 pixmap = GTK_WIDGET(child->data);
578 break;
579 }
580 }
581
582 if ( children )
583 g_list_free(children);
584
585 if ( pixmap && IsPointInsideWidget(pt, pixmap, x, y) )
586 {
587 *flags = wxBK_HITTEST_ONICON;
588 }
589 else if ( IsPointInsideWidget(pt, GTK_WIDGET(nb_page->m_label), x, y) )
590 {
591 *flags = wxBK_HITTEST_ONLABEL;
592 }
593 else
594 {
595 *flags = wxBK_HITTEST_ONITEM;
596 }
597 }
598
599 return i;
600 }
601 }
602
603 if ( flags )
604 {
605 *flags = wxBK_HITTEST_NOWHERE;
606 wxWindowBase * page = GetCurrentPage();
607 if ( page )
608 {
609 // rect origin is in notebook's parent coordinates
610 wxRect rect = page->GetRect();
611
612 // adjust it to the notebook's coordinates
613 wxPoint pos = GetPosition();
614 rect.x -= pos.x;
615 rect.y -= pos.y;
616 if ( rect.Contains( pt ) )
617 *flags |= wxBK_HITTEST_ONPAGE;
618 }
619 }
620
621 return wxNOT_FOUND;
622 }
623
624 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
625 {
626 if (event.IsWindowChange())
627 AdvanceSelection( event.GetDirection() );
628 else
629 event.Skip();
630 }
631
632 #if wxUSE_CONSTRAINTS
633
634 // override these 2 functions to do nothing: everything is done in OnSize
635 void wxNotebook::SetConstraintSizes( bool WXUNUSED(recurse) )
636 {
637 // don't set the sizes of the pages - their correct size is not yet known
638 wxControl::SetConstraintSizes(false);
639 }
640
641 bool wxNotebook::DoPhase( int WXUNUSED(nPhase) )
642 {
643 return true;
644 }
645
646 #endif
647
648 void wxNotebook::DoApplyWidgetStyle(GtkRcStyle *style)
649 {
650 gtk_widget_modify_style(m_widget, style);
651 size_t cnt = m_pagesData.GetCount();
652 for (size_t i = 0; i < cnt; i++)
653 gtk_widget_modify_style(GTK_WIDGET(GetNotebookPage(i)->m_label), style);
654 }
655
656 GdkWindow *wxNotebook::GTKGetWindow(wxArrayGdkWindows& windows) const
657 {
658 windows.push_back(m_widget->window);
659 windows.push_back(GTK_NOTEBOOK(m_widget)->event_window);
660
661 return NULL;
662 }
663
664 // static
665 wxVisualAttributes
666 wxNotebook::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
667 {
668 return GetDefaultAttributesFromGTKWidget(gtk_notebook_new);
669 }
670
671 #endif