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