Added window resize patch to wxFrame
[wxWidgets.git] / src / gtk / notebook.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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 #ifdef __GNUG__
11 #pragma implementation "notebook.h"
12 #endif
13
14 #include "wx/notebook.h"
15 #include "wx/panel.h"
16 #include "wx/utils.h"
17 #include "wx/imaglist.h"
18 #include "wx/intl.h"
19 #include "wx/log.h"
20
21 #include "gdk/gdk.h"
22 #include "gtk/gtk.h"
23 #include "wx/gtk/win_gtk.h"
24 #include "gdk/gdkkeysyms.h"
25
26 //-----------------------------------------------------------------------------
27 // idle system
28 //-----------------------------------------------------------------------------
29
30 extern void wxapp_install_idle_handler();
31 extern bool g_isIdle;
32
33 //-----------------------------------------------------------------------------
34 // data
35 //-----------------------------------------------------------------------------
36
37 extern bool g_blockEventsOnDrag;
38
39 //-----------------------------------------------------------------------------
40 // debug
41 //-----------------------------------------------------------------------------
42
43 #ifdef __WXDEBUG__
44
45 extern void debug_focus_in( GtkWidget* widget, const wxChar* name, const wxChar *window );
46
47 #endif
48
49 //-----------------------------------------------------------------------------
50 // wxNotebookPage
51 //-----------------------------------------------------------------------------
52
53 class wxNotebookPage: public wxObject
54 {
55 public:
56 wxNotebookPage()
57 {
58 m_id = -1;
59 m_text = "";
60 m_image = -1;
61 m_page = (GtkNotebookPage *) NULL;
62 m_client = (wxWindow *) NULL;
63 m_parent = (GtkNotebook *) NULL;
64 m_box = (GtkWidget *) NULL;
65 m_added = FALSE;
66 }
67
68 /*
69 mark page as "added' to the notebook, return FALSE if the page was
70 already added
71 */
72
73 bool Add()
74 {
75 if ( WasAdded() )
76 return FALSE;
77
78 m_added = TRUE;
79 return TRUE;
80 }
81
82 bool WasAdded() const { return m_added; }
83
84 int m_id;
85 wxString m_text;
86 int m_image;
87 GtkNotebookPage *m_page;
88 GtkLabel *m_label;
89 wxWindow *m_client;
90 GtkNotebook *m_parent;
91 GtkWidget *m_box; // in which the label and image are packed
92
93 private:
94 bool m_added;
95 };
96
97 //-----------------------------------------------------------------------------
98 // "switch_page"
99 //-----------------------------------------------------------------------------
100
101 static void gtk_notebook_page_change_callback(GtkNotebook *WXUNUSED(widget),
102 GtkNotebookPage *WXUNUSED(page),
103 gint nPage,
104 gpointer data)
105 {
106 if (g_isIdle) wxapp_install_idle_handler();
107
108 wxNotebook *notebook = (wxNotebook *)data;
109
110 int old = notebook->GetSelection();
111
112 // TODO: emulate PAGE_CHANGING event
113
114 wxNotebookEvent event( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED,
115 notebook->GetId(), nPage, old );
116 event.SetEventObject( notebook );
117 notebook->GetEventHandler()->ProcessEvent( event );
118 }
119
120 //-----------------------------------------------------------------------------
121 // "size_allocate"
122 //-----------------------------------------------------------------------------
123
124 static void gtk_page_size_callback( GtkWidget *WXUNUSED(widget), GtkAllocation* alloc, wxWindow *win )
125 {
126 if (g_isIdle) wxapp_install_idle_handler();
127
128 if ((win->m_x == alloc->x) &&
129 (win->m_y == alloc->y) &&
130 (win->m_width == alloc->width) &&
131 (win->m_height == alloc->height))
132 {
133 return;
134 }
135
136 win->SetSize( alloc->x, alloc->y, alloc->width, alloc->height );
137
138 if (win->GetAutoLayout()) win->Layout();
139 }
140
141 //-----------------------------------------------------------------------------
142 // "key_press_event"
143 //-----------------------------------------------------------------------------
144
145 static gint
146 gtk_notebook_key_press_callback( GtkWidget *widget, GdkEventKey *gdk_event, wxNotebook *notebook )
147 {
148 if (g_isIdle) wxapp_install_idle_handler();
149
150 if (g_blockEventsOnDrag) return FALSE;
151
152 if (!notebook->HasVMT()) return FALSE;
153
154 /* this code makes jumping down from the handles of the notebooks
155 to the actual items in the visible notebook page possible with
156 the down-arrow key */
157
158 if (gdk_event->keyval != GDK_Down) return FALSE;
159
160 if (notebook != notebook->FindFocus()) return FALSE;
161
162 if (notebook->m_pages.GetCount() == 0) return FALSE;
163
164 wxNode *node = notebook->m_pages.Nth( notebook->GetSelection() );
165
166 if (!node) return FALSE;
167
168 wxNotebookPage *page = (wxNotebookPage*) node->Data();
169
170 // don't let others the key event
171 gtk_signal_emit_stop_by_name( GTK_OBJECT(widget), "key_press_event" );
172
173 page->m_client->SetFocus();
174
175 return TRUE;
176 }
177
178 //-----------------------------------------------------------------------------
179 // InsertChild callback for wxNotebook
180 //-----------------------------------------------------------------------------
181
182 static void wxInsertChildInNotebook( wxNotebook* parent, wxWindow* child )
183 {
184 wxNotebookPage *page = new wxNotebookPage();
185
186 page->m_id = parent->GetPageCount();
187
188 page->m_box = gtk_hbox_new (FALSE, 0);
189 gtk_container_border_width(GTK_CONTAINER(page->m_box), 2);
190
191 GtkNotebook *notebook = GTK_NOTEBOOK(parent->m_widget);
192
193 page->m_client = child;
194 gtk_notebook_append_page( notebook, child->m_widget, page->m_box );
195
196 page->m_page = (GtkNotebookPage*) (g_list_last(notebook->children)->data);
197
198 page->m_parent = notebook;
199
200 gtk_signal_connect( GTK_OBJECT(child->m_widget), "size_allocate",
201 GTK_SIGNAL_FUNC(gtk_page_size_callback), (gpointer)child );
202
203 wxASSERT_MSG( page->m_page, _T("Notebook page creation error") );
204
205 parent->m_pages.Append( page );
206 }
207
208 //-----------------------------------------------------------------------------
209 // wxNotebook
210 //-----------------------------------------------------------------------------
211
212 IMPLEMENT_DYNAMIC_CLASS(wxNotebook,wxControl)
213
214 BEGIN_EVENT_TABLE(wxNotebook, wxControl)
215 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
216 END_EVENT_TABLE()
217
218 void wxNotebook::Init()
219 {
220 m_imageList = (wxImageList *) NULL;
221 m_pages.DeleteContents( TRUE );
222 m_idHandler = 0;
223 }
224
225 wxNotebook::wxNotebook()
226 {
227 Init();
228 }
229
230 wxNotebook::wxNotebook( wxWindow *parent, wxWindowID id,
231 const wxPoint& pos, const wxSize& size,
232 long style, const wxString& name )
233 {
234 Init();
235 Create( parent, id, pos, size, style, name );
236 }
237
238 wxNotebook::~wxNotebook()
239 {
240 // don't generate change page events any more
241 if (m_idHandler != 0)
242 gtk_signal_disconnect(GTK_OBJECT(m_widget), m_idHandler);
243
244 DeleteAllPages();
245 }
246
247 bool wxNotebook::Create(wxWindow *parent, wxWindowID id,
248 const wxPoint& pos, const wxSize& size,
249 long style, const wxString& name )
250 {
251 m_needParent = TRUE;
252 m_acceptsFocus = TRUE;
253 m_insertCallback = (wxInsertChildFunction)wxInsertChildInNotebook;
254
255 PreCreation( parent, id, pos, size, style, name );
256
257 m_widget = gtk_notebook_new();
258
259 #ifdef __WXDEBUG__
260 debug_focus_in( m_widget, _T("wxNotebook::m_widget"), name );
261 #endif
262
263 gtk_notebook_set_scrollable( GTK_NOTEBOOK(m_widget), 1 );
264
265 m_idHandler = gtk_signal_connect (
266 GTK_OBJECT(m_widget), "switch_page",
267 GTK_SIGNAL_FUNC(gtk_notebook_page_change_callback),
268 (gpointer)this );
269
270 m_parent->AddChild( this );
271
272 (m_parent->m_insertCallback)( m_parent, this );
273
274 gtk_signal_connect( GTK_OBJECT(m_widget), "key_press_event",
275 GTK_SIGNAL_FUNC(gtk_notebook_key_press_callback), (gpointer)this );
276
277 PostCreation();
278
279 Show( TRUE );
280
281 return TRUE;
282 }
283
284 int wxNotebook::GetSelection() const
285 {
286 wxCHECK_MSG( m_widget != NULL, -1, _T("invalid notebook") );
287
288 if (m_pages.Number() == 0) return -1;
289
290 GtkNotebookPage *g_page = GTK_NOTEBOOK(m_widget)->cur_page;
291 if (!g_page) return -1;
292
293 wxNotebookPage *page = (wxNotebookPage *) NULL;
294
295 wxNode *node = m_pages.First();
296 while (node)
297 {
298 page = (wxNotebookPage*)node->Data();
299
300 if ((page->m_page == g_page) || (page->m_page == (GtkNotebookPage*)NULL))
301 {
302 // page->m_page is NULL directly after gtk_notebook_append. gtk emits
303 // "switch_page" then and we ask for GetSelection() in the handler for
304 // "switch_page". otherwise m_page should never be NULL. all this
305 // might also be wrong.
306 break;
307 }
308 node = node->Next();
309 }
310
311 wxCHECK_MSG( node != NULL, -1, _T("wxNotebook: no selection?") );
312
313 return page->m_id;
314 }
315
316 int wxNotebook::GetPageCount() const
317 {
318 // count only the pages which were already added to the notebook for MSW
319 // compatibility (and, in fact, this behaviour makes more sense anyhow
320 // because only the added pages are shown)
321
322 int n = 0;
323 for ( wxNode *node = m_pages.First(); node; node = node->Next() )
324 {
325 wxNotebookPage *page = (wxNotebookPage*)node->Data();
326
327 if (page->WasAdded()) n++;
328 }
329
330 return n;
331 }
332
333 int wxNotebook::GetRowCount() const
334 {
335 return 1;
336 }
337
338 wxString wxNotebook::GetPageText( int page ) const
339 {
340 wxCHECK_MSG( m_widget != NULL, _T(""), _T("invalid notebook") );
341
342 wxNotebookPage* nb_page = GetNotebookPage(page);
343 if (nb_page)
344 return nb_page->m_text;
345 else
346 return "";
347 }
348
349 int wxNotebook::GetPageImage( int page ) const
350 {
351 wxCHECK_MSG( m_widget != NULL, 0, _T("invalid notebook") );
352
353 wxNotebookPage* nb_page = GetNotebookPage(page);
354 if (nb_page)
355 return nb_page->m_image;
356 else
357 return 0;
358 }
359
360 wxNotebookPage* wxNotebook::GetNotebookPage(int page) const
361 {
362 wxCHECK_MSG( m_widget != NULL, (wxNotebookPage*)NULL, _T("invalid notebook") );
363
364 wxNotebookPage *nb_page = (wxNotebookPage *) NULL;
365
366 wxNode *node = m_pages.First();
367 while (node)
368 {
369 nb_page = (wxNotebookPage*)node->Data();
370 if (nb_page->m_id == page)
371 return nb_page;
372 node = node->Next();
373 }
374
375 wxFAIL_MSG( _T("Notebook page not found!") );
376
377 return (wxNotebookPage *) NULL;
378 }
379
380 int wxNotebook::SetSelection( int page )
381 {
382 wxCHECK_MSG( m_widget != NULL, -1, _T("invalid notebook") );
383
384 int selOld = GetSelection();
385 wxNotebookPage* nb_page = GetNotebookPage(page);
386
387 if (!nb_page) return -1;
388
389 int page_num = 0;
390 GList *child = GTK_NOTEBOOK(m_widget)->children;
391 while (child)
392 {
393 if (nb_page->m_page == (GtkNotebookPage*)child->data) break;
394 page_num++;
395 child = child->next;
396 }
397
398 if (!child) return -1;
399
400 gtk_notebook_set_page( GTK_NOTEBOOK(m_widget), page_num );
401
402 return selOld;
403 }
404
405 void wxNotebook::AdvanceSelection( bool bForward )
406 {
407 wxCHECK_RET( m_widget != NULL, _T("invalid notebook") );
408
409 int sel = GetSelection();
410 int max = GetPageCount();
411
412 if (bForward)
413 SetSelection( sel == max ? 0 : sel + 1 );
414 else
415 SetSelection( sel == 0 ? max-1 : sel - 1 );
416 }
417
418 void wxNotebook::SetImageList( wxImageList* imageList )
419 {
420 m_imageList = imageList;
421 }
422
423 bool wxNotebook::SetPageText( int page, const wxString &text )
424 {
425 wxCHECK_MSG( m_widget != NULL, FALSE, _T("invalid notebook") );
426
427 wxNotebookPage* nb_page = GetNotebookPage(page);
428
429 wxCHECK_MSG( nb_page, FALSE, _T("SetPageText: invalid page index") );
430
431 nb_page->m_text = text;
432
433 gtk_label_set(nb_page->m_label, nb_page->m_text.mbc_str());
434
435 return TRUE;
436 }
437
438 bool wxNotebook::SetPageImage( int page, int image )
439 {
440 /* HvdH 28-12-98: now it works, but it's a bit of a kludge */
441
442 wxNotebookPage* nb_page = GetNotebookPage(page);
443
444 if (!nb_page) return FALSE;
445
446 /* Optimization posibility: return immediately if image unchanged.
447 * Not enabled because it may break existing (stupid) code that
448 * manipulates the imagelist to cycle images */
449
450 /* if (image == nb_page->m_image) return TRUE; */
451
452 /* For different cases:
453 1) no image -> no image
454 2) image -> no image
455 3) no image -> image
456 4) image -> image */
457
458 if (image == -1 && nb_page->m_image == -1)
459 return TRUE; /* Case 1): Nothing to do. */
460
461 GtkWidget *pixmapwid = (GtkWidget*) NULL;
462
463 if (nb_page->m_image != -1)
464 {
465 /* Case 2) or 4). There is already an image in the gtkhbox. Let's find it */
466
467 GList *child = gtk_container_children(GTK_CONTAINER(nb_page->m_box));
468 while (child)
469 {
470 if (GTK_IS_PIXMAP(child->data))
471 {
472 pixmapwid = GTK_WIDGET(child->data);
473 break;
474 }
475 child = child->next;
476 }
477
478 /* We should have the pixmap widget now */
479 wxASSERT(pixmapwid != NULL);
480
481 if (image == -1)
482 {
483 /* If there's no new widget, just remove the old from the box */
484 gtk_container_remove(GTK_CONTAINER(nb_page->m_box), pixmapwid);
485 nb_page->m_image = -1;
486
487 return TRUE; /* Case 2) */
488 }
489 }
490
491 /* Only cases 3) and 4) left */
492 wxASSERT( m_imageList != NULL ); /* Just in case */
493
494 /* Construct the new pixmap */
495 const wxBitmap *bmp = m_imageList->GetBitmap(image);
496 GdkPixmap *pixmap = bmp->GetPixmap();
497 GdkBitmap *mask = (GdkBitmap*) NULL;
498 if ( bmp->GetMask() )
499 {
500 mask = bmp->GetMask()->GetBitmap();
501 }
502
503 if (pixmapwid == NULL)
504 {
505 /* Case 3) No old pixmap. Create a new one and prepend it to the hbox */
506 pixmapwid = gtk_pixmap_new (pixmap, mask );
507
508 /* CHECKME: Are these pack flags okay? */
509 gtk_box_pack_start(GTK_BOX(nb_page->m_box), pixmapwid, FALSE, FALSE, 3);
510 gtk_widget_show(pixmapwid);
511 }
512 else
513 {
514 /* Case 4) Simply replace the pixmap */
515 gtk_pixmap_set(GTK_PIXMAP(pixmapwid), pixmap, mask);
516 }
517
518 nb_page->m_image = image;
519
520 return TRUE;
521 }
522
523 void wxNotebook::SetPageSize( const wxSize &WXUNUSED(size) )
524 {
525 wxFAIL_MSG( _T("wxNotebook::SetPageSize not implemented") );
526 }
527
528 void wxNotebook::SetPadding( const wxSize &WXUNUSED(padding) )
529 {
530 wxFAIL_MSG( _T("wxNotebook::SetPadding not implemented") );
531 }
532
533 void wxNotebook::SetTabSize(const wxSize& sz)
534 {
535 wxFAIL_MSG( _T("wxNotebook::SetTabSize not implemented") );
536 }
537
538 bool wxNotebook::DeleteAllPages()
539 {
540 wxCHECK_MSG( m_widget != NULL, FALSE, _T("invalid notebook") );
541
542 wxNode *page_node = m_pages.First();
543 while (page_node)
544 {
545 wxNotebookPage *page = (wxNotebookPage*)page_node->Data();
546
547 DeletePage( page->m_id );
548
549 page_node = m_pages.First();
550 }
551
552 return TRUE;
553 }
554
555 bool wxNotebook::DeletePage( int page )
556 {
557 wxNotebookPage* nb_page = GetNotebookPage(page);
558 if (!nb_page) return FALSE;
559
560 int page_num = 0;
561 GList *child = GTK_NOTEBOOK(m_widget)->children;
562 while (child)
563 {
564 if (nb_page->m_page == (GtkNotebookPage*)child->data) break;
565 page_num++;
566 child = child->next;
567 }
568
569 wxCHECK_MSG( child != NULL, FALSE, _T("illegal notebook index") );
570
571 delete nb_page->m_client;
572
573 m_pages.DeleteObject( nb_page );
574
575 /* adjust the notebook page numbers so that
576 m_id reflects the current position, Daniel Paull */
577 int count = 0;
578 wxNode *node = m_pages.First();
579 wxNotebookPage *pagePtr = (wxNotebookPage *) NULL;
580 while (node)
581 {
582 pagePtr = (wxNotebookPage*)node->Data();
583 pagePtr->m_id = count++;
584 node = node->Next();
585 }
586
587 return TRUE;
588 }
589
590 bool wxNotebook::RemovePage( int page )
591 {
592 wxNotebookPage* nb_page = GetNotebookPage(page);
593 if (!nb_page) return FALSE;
594
595 int page_num = 0;
596 GList *child = GTK_NOTEBOOK(m_widget)->children;
597 while (child)
598 {
599 if (nb_page->m_page == (GtkNotebookPage*)child->data) break;
600 page_num++;
601 child = child->next;
602 }
603
604 wxCHECK_MSG( child != NULL, FALSE, _T("illegal notebook index") );
605
606 gtk_notebook_remove_page( GTK_NOTEBOOK(m_widget), page_num );
607
608 m_pages.DeleteObject( nb_page );
609
610 return TRUE;
611 }
612
613 bool wxNotebook::AddPage(wxWindow* win, const wxString& text,
614 bool select, int imageId)
615 {
616 wxCHECK_MSG( m_widget != NULL, FALSE, _T("invalid notebook") );
617
618 /* we've created the notebook page in AddChild(). Now we just have to set
619 the caption for the page and set the others parameters. */
620
621 wxNotebookPage *page = (wxNotebookPage *) NULL;
622
623 wxNode *node = m_pages.First();
624 while (node)
625 {
626 page = (wxNotebookPage*)node->Data();
627 if ( page->m_client == win ) break;
628 node = node->Next();
629 }
630
631 wxCHECK_MSG( page != NULL, FALSE,
632 _T("Can't add a page whose parent is not the notebook!") );
633
634 wxCHECK_MSG( page->Add(), FALSE,
635 _T("Can't add the same page twice to a notebook.") );
636
637 if (imageId != -1)
638 {
639 wxASSERT( m_imageList != NULL );
640
641 const wxBitmap *bmp = m_imageList->GetBitmap(imageId);
642 GdkPixmap *pixmap = bmp->GetPixmap();
643 GdkBitmap *mask = (GdkBitmap*) NULL;
644 if ( bmp->GetMask() )
645 {
646 mask = bmp->GetMask()->GetBitmap();
647 }
648
649 GtkWidget *pixmapwid = gtk_pixmap_new (pixmap, mask );
650
651 gtk_box_pack_start(GTK_BOX(page->m_box), pixmapwid, FALSE, FALSE, 3);
652
653 gtk_widget_show(pixmapwid);
654 }
655
656 /* then set the attributes */
657 page->m_text = text;
658 if (page->m_text.IsEmpty()) page->m_text = _T("");
659 page->m_image = imageId;
660 page->m_label = (GtkLabel *)gtk_label_new(page->m_text.mbc_str());
661 gtk_box_pack_end( GTK_BOX(page->m_box), (GtkWidget *)page->m_label, FALSE, FALSE, 3);
662
663 /* @@@: what does this do? do we still need it?
664 gtk_misc_set_alignment(GTK_MISC(page->m_label), 0.0, 0.5); */
665
666 gtk_widget_show((GtkWidget *)page->m_label);
667
668 if (select) SetSelection( GetPageCount()-1 );
669
670 return TRUE;
671 }
672
673 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
674 {
675 if (event.IsWindowChange())
676 AdvanceSelection( event.GetDirection() );
677 else
678 event.Skip();
679 }
680
681 wxWindow *wxNotebook::GetPage( int page ) const
682 {
683 wxCHECK_MSG( m_widget != NULL, (wxWindow*) NULL, _T("invalid notebook") );
684
685 wxNotebookPage* nb_page = GetNotebookPage(page);
686 if (!nb_page)
687 return (wxWindow *) NULL;
688 else
689 return nb_page->m_client;
690 }
691
692 // override these 2 functions to do nothing: everything is done in OnSize
693 void wxNotebook::SetConstraintSizes( bool WXUNUSED(recurse) )
694 {
695 // don't set the sizes of the pages - their correct size is not yet known
696 wxControl::SetConstraintSizes(FALSE);
697 }
698
699 bool wxNotebook::DoPhase( int WXUNUSED(nPhase) )
700 {
701 return TRUE;
702 }
703
704 void wxNotebook::ApplyWidgetStyle()
705 {
706 SetWidgetStyle();
707 gtk_widget_set_style( m_widget, m_widgetStyle );
708 }
709
710 //-----------------------------------------------------------------------------
711 // wxNotebookEvent
712 //-----------------------------------------------------------------------------
713
714 IMPLEMENT_DYNAMIC_CLASS(wxNotebookEvent, wxNotifyEvent)
715