gtknotebook.c 131 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
1
2
3
4
/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6
7
8
9
10
11
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
16
17
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
18
 */
19
20

/*
21
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22
23
24
25
26
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

Elliot Lee's avatar
Elliot Lee committed
27
#include "gtknotebook.h"
28
#include "gtksignal.h"
29
30
31
32
33
34
#include "gtkmain.h"
#include "gtkmenu.h"
#include "gtkmenuitem.h"
#include "gtklabel.h"
#include <gdk/gdkkeysyms.h>
#include <stdio.h>
35
#include "gtkintl.h"
36
#include "gtkmarshalers.h"
37
#include "gtkbindings.h"
Elliot Lee's avatar
Elliot Lee committed
38
39
40
41


#define TAB_OVERLAP    2
#define TAB_CURVATURE  1
42
43
#define ARROW_SIZE     12
#define ARROW_SPACING  0
44
45
#define NOTEBOOK_INIT_SCROLL_DELAY (200)
#define NOTEBOOK_SCROLL_DELAY      (100)
Elliot Lee's avatar
Elliot Lee committed
46
47


48
49
enum {
  SWITCH_PAGE,
50
51
  FOCUS_TAB,
  SELECT_PAGE,
52
  CHANGE_CURRENT_PAGE,
53
54
55
  LAST_SIGNAL
};

56
57
58
59
60
enum {
  STEP_PREV,
  STEP_NEXT
};

61
enum {
62
63
64
65
66
67
68
69
70
71
72
  PROP_0,
  PROP_TAB_POS,
  PROP_SHOW_TABS,
  PROP_SHOW_BORDER,
  PROP_SCROLLABLE,
  PROP_TAB_BORDER,
  PROP_TAB_HBORDER,
  PROP_TAB_VBORDER,
  PROP_PAGE,
  PROP_ENABLE_POPUP,
  PROP_HOMOGENEOUS
73
74
75
};

enum {
Tim Janik's avatar
Tim Janik committed
76
77
78
79
80
81
82
  CHILD_PROP_0,
  CHILD_PROP_TAB_LABEL,
  CHILD_PROP_MENU_LABEL,
  CHILD_PROP_POSITION,
  CHILD_PROP_TAB_EXPAND,
  CHILD_PROP_TAB_FILL,
  CHILD_PROP_TAB_PACK
83
84
};

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#define GTK_NOTEBOOK_PAGE(_glist_)         ((GtkNotebookPage *)((GList *)(_glist_))->data)

struct _GtkNotebookPage
{
  GtkWidget *child;
  GtkWidget *tab_label;
  GtkWidget *menu_label;

  guint default_menu : 1;	/* If true, we create the menu label ourself */
  guint default_tab  : 1;	/* If true, we create the tab label ourself */
  guint expand       : 1;
  guint fill         : 1;
  guint pack         : 1;

  GtkRequisition requisition;
  GtkAllocation allocation;
101

102
  guint mnemonic_activate_signal;
103
104
105
106
};

#ifdef G_DISABLE_CHECKS
#define CHECK_FIND_CHILD(notebook, child)                           \
107
 gtk_notebook_find_child (notebook, child, G_STRLOC)
108
109
#else
#define CHECK_FIND_CHILD(notebook, child)                           \
110
 gtk_notebook_find_child (notebook, child, NULL)
111
112
#endif
 
113
/*** GtkNotebook Methods ***/
114
115
static void gtk_notebook_class_init          (GtkNotebookClass *klass);
static void gtk_notebook_init                (GtkNotebook      *notebook);
116

117
118
119
120
static void gtk_notebook_select_page         (GtkNotebook       *notebook,
                                              gboolean           move_focus);
static void gtk_notebook_focus_tab           (GtkNotebook       *notebook,
                                              GtkNotebookTab     type);
121
122
123
static void gtk_notebook_change_current_page (GtkNotebook       *notebook,
					      gint               offset);

124

125
/*** GtkObject Methods ***/
126
static void gtk_notebook_destroy             (GtkObject        *object);
127
128
129
130
131
132
133
134
static void gtk_notebook_set_property	     (GObject         *object,
					      guint            prop_id,
					      const GValue    *value,
					      GParamSpec      *pspec);
static void gtk_notebook_get_property	     (GObject         *object,
					      guint            prop_id,
					      GValue          *value,
					      GParamSpec      *pspec);
135

136
/*** GtkWidget Methods ***/
137
138
139
static void gtk_notebook_map                 (GtkWidget        *widget);
static void gtk_notebook_unmap               (GtkWidget        *widget);
static void gtk_notebook_realize             (GtkWidget        *widget);
140
static void gtk_notebook_unrealize           (GtkWidget        *widget);
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
static void gtk_notebook_size_request        (GtkWidget        *widget,
					      GtkRequisition   *requisition);
static void gtk_notebook_size_allocate       (GtkWidget        *widget,
					      GtkAllocation    *allocation);
static gint gtk_notebook_expose              (GtkWidget        *widget,
					      GdkEventExpose   *event);
static gint gtk_notebook_button_press        (GtkWidget        *widget,
					      GdkEventButton   *event);
static gint gtk_notebook_button_release      (GtkWidget        *widget,
					      GdkEventButton   *event);
static gint gtk_notebook_enter_notify        (GtkWidget        *widget,
					      GdkEventCrossing *event);
static gint gtk_notebook_leave_notify        (GtkWidget        *widget,
					      GdkEventCrossing *event);
static gint gtk_notebook_motion_notify       (GtkWidget        *widget,
					      GdkEventMotion   *event);
157
158
159
static gint gtk_notebook_focus_in            (GtkWidget        *widget,
					      GdkEventFocus    *event);
static void gtk_notebook_draw_focus          (GtkWidget        *widget);
160
161
static gint gtk_notebook_focus               (GtkWidget        *widget,
					      GtkDirectionType  direction);
162

163
/*** GtkContainer Methods ***/
Tim Janik's avatar
Tim Janik committed
164
static void gtk_notebook_set_child_property  (GtkContainer     *container,
165
					      GtkWidget        *child,
Tim Janik's avatar
Tim Janik committed
166
167
168
169
					      guint             property_id,
					      const GValue     *value,
					      GParamSpec       *pspec);
static void gtk_notebook_get_child_property  (GtkContainer     *container,
170
					      GtkWidget        *child,
Tim Janik's avatar
Tim Janik committed
171
172
173
					      guint             property_id,
					      GValue           *value,
					      GParamSpec       *pspec);
174
175
176
177
static void gtk_notebook_add                 (GtkContainer     *container,
					      GtkWidget        *widget);
static void gtk_notebook_remove              (GtkContainer     *container,
					      GtkWidget        *widget);
178
179
180
static void gtk_notebook_set_focus_child     (GtkContainer     *container,
					      GtkWidget        *child);
static GtkType gtk_notebook_child_type       (GtkContainer     *container);
181
182
static void gtk_notebook_forall              (GtkContainer     *container,
					      gboolean		include_internals,
183
184
					      GtkCallback       callback,
					      gpointer          callback_data);
185

186
/*** GtkNotebook Private Functions ***/
187
188
static void gtk_notebook_redraw_tabs         (GtkNotebook      *notebook);
static void gtk_notebook_redraw_arrows       (GtkNotebook      *notebook);
189
190
191
192
193
194
195
196
static void gtk_notebook_focus_changed       (GtkNotebook      *notebook,
					      GtkNotebookPage  *old_page);
static void gtk_notebook_real_remove         (GtkNotebook      *notebook,
					      GList            *list);
static void gtk_notebook_update_labels       (GtkNotebook      *notebook);
static gint gtk_notebook_timer               (GtkNotebook      *notebook);
static gint gtk_notebook_page_compare        (gconstpointer     a,
					      gconstpointer     b);
197
static GList* gtk_notebook_find_child        (GtkNotebook      *notebook,
198
199
					      GtkWidget        *child,
					      const gchar      *function);
200
201
202
203
204
205
206
static gint  gtk_notebook_real_page_position (GtkNotebook      *notebook,
					      GList            *list);
static GList * gtk_notebook_search_page      (GtkNotebook      *notebook,
					      GList            *list,
					      gint              direction,
					      gboolean          find_visible);

207
/*** GtkNotebook Drawing Functions ***/
208
209
static void gtk_notebook_paint               (GtkWidget        *widget,
					      GdkRectangle     *area);
210
211
212
static void gtk_notebook_draw_tab            (GtkNotebook      *notebook,
					      GtkNotebookPage  *page,
					      GdkRectangle     *area);
213
214
215
static void gtk_notebook_draw_arrow          (GtkNotebook      *notebook,
					      guint             arrow);

216
/*** GtkNotebook Size Allocate Functions ***/
217
static void gtk_notebook_pages_allocate      (GtkNotebook      *notebook);
218
219
220
static void gtk_notebook_page_allocate       (GtkNotebook      *notebook,
					      GtkNotebookPage  *page,
					      GtkAllocation    *allocation);
221
222
static void gtk_notebook_calc_tabs           (GtkNotebook      *notebook,
			                      GList            *start,
223
224
225
					      GList           **end,
					      gint             *tab_space,
					      guint             direction);
226

227
/*** GtkNotebook Page Switch Methods ***/
228
static void gtk_notebook_real_switch_page    (GtkNotebook      *notebook,
Lars Hamann's avatar
Lars Hamann committed
229
					      GtkNotebookPage  *page,
230
					      guint             page_num);
231

232
/*** GtkNotebook Page Switch Functions ***/
233
234
static void gtk_notebook_switch_page         (GtkNotebook      *notebook,
					      GtkNotebookPage  *page,
235
					      gint              page_num);
236
237
static gint gtk_notebook_page_select         (GtkNotebook      *notebook,
					      gboolean          move_focus);
238
239
static void gtk_notebook_switch_focus_tab    (GtkNotebook      *notebook,
                                              GList            *new_child);
240
241
static void gtk_notebook_menu_switch_page    (GtkWidget        *widget,
					      GtkNotebookPage  *page);
242

243
/*** GtkNotebook Menu Functions ***/
244
245
246
247
static void gtk_notebook_menu_item_create    (GtkNotebook      *notebook,
					      GList            *list);
static void gtk_notebook_menu_label_unparent (GtkWidget        *widget,
					      gpointer          data);
248
249
static void gtk_notebook_menu_detacher       (GtkWidget        *widget,
					      GtkMenu          *menu);
250

Elliot Lee's avatar
Elliot Lee committed
251

252
static GtkContainerClass *parent_class = NULL;
253
static guint notebook_signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
254

255
GtkType
256
gtk_notebook_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
257
{
258
  static GtkType notebook_type = 0;
Elliot Lee's avatar
Elliot Lee committed
259
260
261

  if (!notebook_type)
    {
262
      static const GtkTypeInfo notebook_info =
Elliot Lee's avatar
Elliot Lee committed
263
264
265
266
267
268
      {
	"GtkNotebook",
	sizeof (GtkNotebook),
	sizeof (GtkNotebookClass),
	(GtkClassInitFunc) gtk_notebook_class_init,
	(GtkObjectInitFunc) gtk_notebook_init,
269
270
	/* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
271
        (GtkClassInitFunc) NULL,
Elliot Lee's avatar
Elliot Lee committed
272
273
274
275
276
277
278
279
280
281
282
      };

      notebook_type = gtk_type_unique (gtk_container_get_type (), &notebook_info);
    }

  return notebook_type;
}

static void
gtk_notebook_class_init (GtkNotebookClass *class)
{
Tim Janik's avatar
Tim Janik committed
283
284
285
286
  GObjectClass   *gobject_class = G_OBJECT_CLASS (class);
  GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
287
288
  GtkBindingSet *binding_set;
  
Tim Janik's avatar
Tim Janik committed
289
  parent_class = g_type_class_peek_parent (class);
290

291
292
  gobject_class->set_property = gtk_notebook_set_property;
  gobject_class->get_property = gtk_notebook_get_property;
293
294
  object_class->destroy = gtk_notebook_destroy;

Elliot Lee's avatar
Elliot Lee committed
295
296
297
  widget_class->map = gtk_notebook_map;
  widget_class->unmap = gtk_notebook_unmap;
  widget_class->realize = gtk_notebook_realize;
298
  widget_class->unrealize = gtk_notebook_unrealize;
Elliot Lee's avatar
Elliot Lee committed
299
300
301
302
  widget_class->size_request = gtk_notebook_size_request;
  widget_class->size_allocate = gtk_notebook_size_allocate;
  widget_class->expose_event = gtk_notebook_expose;
  widget_class->button_press_event = gtk_notebook_button_press;
303
304
305
306
307
  widget_class->button_release_event = gtk_notebook_button_release;
  widget_class->enter_notify_event = gtk_notebook_enter_notify;
  widget_class->leave_notify_event = gtk_notebook_leave_notify;
  widget_class->motion_notify_event = gtk_notebook_motion_notify;
  widget_class->focus_in_event = gtk_notebook_focus_in;
308
309
  widget_class->focus = gtk_notebook_focus;
  
Elliot Lee's avatar
Elliot Lee committed
310
311
  container_class->add = gtk_notebook_add;
  container_class->remove = gtk_notebook_remove;
312
  container_class->forall = gtk_notebook_forall;
Lars Hamann's avatar
Lars Hamann committed
313
  container_class->set_focus_child = gtk_notebook_set_focus_child;
Tim Janik's avatar
Tim Janik committed
314
315
  container_class->get_child_property = gtk_notebook_get_child_property;
  container_class->set_child_property = gtk_notebook_set_child_property;
316
  container_class->child_type = gtk_notebook_child_type;
317

318
  class->switch_page = gtk_notebook_real_switch_page;
319

320
321
  class->focus_tab = gtk_notebook_focus_tab;
  class->select_page = gtk_notebook_select_page;
322
  class->change_current_page = gtk_notebook_change_current_page;
323
  
324
325
326
327
328
329
330
331
332
  g_object_class_install_property (gobject_class,
				   PROP_PAGE,
				   g_param_spec_int ("page",
 						     _("Page"),
 						     _("The index of the current page"),
 						     0,
 						     G_MAXINT,
 						     0,
 						     G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
333
334
335
  g_object_class_install_property (gobject_class,
				   PROP_TAB_POS,
				   g_param_spec_enum ("tab_pos",
336
337
338
339
340
 						      _("Tab Position"),
 						      _("Which side of the notebook holds the tabs"),
 						      GTK_TYPE_POSITION_TYPE,
 						      GTK_POS_TOP,
 						      G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
341
342
343
  g_object_class_install_property (gobject_class,
				   PROP_TAB_BORDER,
				   g_param_spec_uint ("tab_border",
344
345
346
347
348
349
 						      _("Tab Border"),
 						      _("Width of the border around the tab labels"),
 						      0,
 						      G_MAXUINT,
 						      2,
 						      G_PARAM_WRITABLE));
Tim Janik's avatar
Tim Janik committed
350
351
352
  g_object_class_install_property (gobject_class,
				   PROP_TAB_HBORDER,
				   g_param_spec_uint ("tab_hborder",
353
354
355
356
357
358
 						      _("Horizontal Tab Border"),
 						      _("Width of the horizontal border of tab labels"),
 						      0,
 						      G_MAXUINT,
 						      2,
 						      G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
359
360
361
  g_object_class_install_property (gobject_class,
				   PROP_TAB_VBORDER,
				   g_param_spec_uint ("tab_vborder",
362
363
364
365
366
367
 						      _("Vertical Tab Border"),
 						      _("Width of the vertical border of tab labels"),
 						      0,
 						      G_MAXUINT,
 						      2,
 						      G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
368
369
370
  g_object_class_install_property (gobject_class,
				   PROP_SHOW_TABS,
				   g_param_spec_boolean ("show_tabs",
371
372
373
374
 							 _("Show Tabs"),
 							 _("Whether tabs should be shown or not"),
 							 TRUE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
375
376
377
  g_object_class_install_property (gobject_class,
				   PROP_SHOW_BORDER,
				   g_param_spec_boolean ("show_border",
378
379
380
381
 							 _("Show Border"),
 							 _("Whether the border should be shown or not"),
 							 TRUE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
382
383
384
  g_object_class_install_property (gobject_class,
				   PROP_SCROLLABLE,
				   g_param_spec_boolean ("scrollable",
385
386
387
388
 							 _("Scrollable"),
 							 _("If TRUE, scroll arrows are added if there are to many tabs to fit"),
 							 FALSE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
389
390
391
  g_object_class_install_property (gobject_class,
				   PROP_ENABLE_POPUP,
				   g_param_spec_boolean ("enable_popup",
392
393
394
395
 							 _("Enable Popup"),
 							 _("If TRUE, pressing the right mouse button on the notebook pops up a menu that you can use to go to a page"),
 							 FALSE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
396
397
398
  g_object_class_install_property (gobject_class,
				   PROP_HOMOGENEOUS,
				   g_param_spec_boolean ("homogeneous",
399
400
401
 							 _("Homogeneous"),
 							 _("Whether tabs should have homogeneous sizes"),
 							 FALSE,
Tim Janik's avatar
Tim Janik committed
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
							 G_PARAM_READWRITE));

  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_LABEL,
					      g_param_spec_string ("tab_label", NULL, NULL,
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_MENU_LABEL,
					      g_param_spec_string ("menu_label", NULL, NULL,
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_POSITION,
					      g_param_spec_int ("position", NULL, NULL,
								-1, G_MAXINT, 0,
								G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_EXPAND,
					      g_param_spec_boolean ("tab_expand", NULL, NULL,
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_FILL,
					      g_param_spec_boolean ("tab_fill", NULL, NULL,
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_PACK,
431
432
433
					      g_param_spec_enum ("tab_pack", NULL, NULL,
								 GTK_TYPE_PACK_TYPE, GTK_PACK_START,
								 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
434
  
435
436
437
438
439
  notebook_signals[SWITCH_PAGE] =
    gtk_signal_new ("switch_page",
		    GTK_RUN_LAST,
		    GTK_CLASS_TYPE (object_class),
		    GTK_SIGNAL_OFFSET (GtkNotebookClass, switch_page),
440
		    _gtk_marshal_VOID__POINTER_UINT,
441
442
443
		    GTK_TYPE_NONE, 2,
		    GTK_TYPE_POINTER,
		    GTK_TYPE_UINT);
444
  notebook_signals[FOCUS_TAB] = 
445
446
447
448
449
    g_signal_new ("focus_tab",
                  G_TYPE_FROM_CLASS (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, focus_tab),
                  NULL, NULL,
450
                  _gtk_marshal_VOID__ENUM,
451
452
                  G_TYPE_NONE, 1,
                  GTK_TYPE_NOTEBOOK_TAB);
453
  notebook_signals[SELECT_PAGE] = 
454
455
456
457
458
    g_signal_new ("select_page",
                  G_TYPE_FROM_CLASS (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, select_page),
                  NULL, NULL,
459
                  _gtk_marshal_VOID__BOOLEAN,
460
461
                  G_TYPE_NONE, 1,
                  G_TYPE_BOOLEAN);
462
463
464
465
466
467
468
469
470
  notebook_signals[CHANGE_CURRENT_PAGE] = 
    g_signal_new ("change_current_page",
                  G_TYPE_FROM_CLASS (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, change_current_page),
                  NULL, NULL,
                  gtk_marshal_VOID__INT,
                  G_TYPE_NONE, 1,
                  G_TYPE_INT);
471
  
472
473
474
475
476
477
478
479
480
481
482
483
484
  binding_set = gtk_binding_set_by_class (object_class);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_Return, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, TRUE);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Enter, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, TRUE);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
485
486
487
488
489
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
  
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
  gtk_binding_entry_add_signal (binding_set,
                                GDK_Home, 0,
                                "focus_tab", 1, 
                                GTK_TYPE_NOTEBOOK_TAB, GTK_NOTEBOOK_TAB_FIRST);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Home, 0,
                                "focus_tab", 1, 
                                GTK_TYPE_NOTEBOOK_TAB, GTK_NOTEBOOK_TAB_FIRST);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_End, 0,
                                "focus_tab", 1, 
                                GTK_TYPE_NOTEBOOK_TAB, GTK_NOTEBOOK_TAB_LAST);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_End, 0,
                                "focus_tab", 1, 
                                GTK_TYPE_NOTEBOOK_TAB, GTK_NOTEBOOK_TAB_LAST);
506
507
508
509
510
511
512
513
514

  gtk_binding_entry_add_signal (binding_set,
                                GDK_Page_Up, GDK_CONTROL_MASK,
                                "change_current_page", 1,
                                G_TYPE_INT, -1);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_Page_Down, GDK_CONTROL_MASK,
                                "change_current_page", 1,
                                G_TYPE_INT, 1);
Elliot Lee's avatar
Elliot Lee committed
515
516
}

517
static void
518
519
gtk_notebook_init (GtkNotebook *notebook)
{
520
  GTK_WIDGET_SET_FLAGS (notebook, GTK_CAN_FOCUS | GTK_RECEIVES_DEFAULT);
521
  GTK_WIDGET_SET_FLAGS (notebook, GTK_NO_WINDOW);
522
523
524
525
526

  notebook->cur_page = NULL;
  notebook->children = NULL;
  notebook->first_tab = NULL;
  notebook->focus_tab = NULL;
527
  notebook->event_window = NULL;
528
529
530
531
532
533
534
535
536
537
538
539
540
541
  notebook->menu = NULL;

  notebook->tab_hborder = 2;
  notebook->tab_vborder = 2;

  notebook->show_tabs = TRUE;
  notebook->show_border = TRUE;
  notebook->tab_pos = GTK_POS_TOP;
  notebook->scrollable = FALSE;
  notebook->in_child = 0;
  notebook->click_child = 0;
  notebook->button = 0;
  notebook->need_timer = 0;
  notebook->child_has_focus = FALSE;
542
  notebook->have_visible_child = FALSE;
543
544
}

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
static void
gtk_notebook_select_page (GtkNotebook *notebook,
                          gboolean     move_focus)
{
  gtk_notebook_page_select (notebook, move_focus);
}

static void
gtk_notebook_focus_tab (GtkNotebook       *notebook,
                        GtkNotebookTab     type)
{
  GList *list;
  
  switch (type)
    {
    case GTK_NOTEBOOK_TAB_FIRST:
      list = gtk_notebook_search_page (notebook, NULL, STEP_NEXT, TRUE);
      if (list)
	gtk_notebook_switch_focus_tab (notebook, list);
      break;
    case GTK_NOTEBOOK_TAB_LAST:
      list = gtk_notebook_search_page (notebook, NULL, STEP_PREV, TRUE);
      if (list)
	gtk_notebook_switch_focus_tab (notebook, list);
      break;
    }
}

573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
static void
gtk_notebook_change_current_page (GtkNotebook *notebook,
				  gint         offset)
{
  GList *current = NULL;
  
  if (notebook->cur_page)
    current = g_list_find (notebook->children, notebook->cur_page);

  while (offset != 0)
    {
      current = gtk_notebook_search_page (notebook, current, offset < 0 ? STEP_PREV : STEP_NEXT, TRUE);
      offset += offset < 0 ? 1 : -1;
    }

  if (current)
    gtk_notebook_switch_page (notebook, current->data, -1);
}

592
593
594
595
596
597
598
/**
 * gtk_notebook_new:
 * 
 * Creates a new #GtkNotebook widget with no pages.

 * Return value: the newly created #GtkNotebook
 **/
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
GtkWidget*
gtk_notebook_new (void)
{
  return GTK_WIDGET (gtk_type_new (gtk_notebook_get_type ()));
}

/* Private GtkObject Methods :
 * 
 * gtk_notebook_destroy
 * gtk_notebook_set_arg
 * gtk_notebook_get_arg
 */
static void
gtk_notebook_destroy (GtkObject *object)
{
614
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
615
616
617
618
619
620
621
622
  
  if (notebook->menu)
    gtk_notebook_popup_disable (notebook);

  GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

static void
623
624
625
626
gtk_notebook_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
627
628
629
630
631
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

632
  switch (prop_id)
633
    {
634
635
    case PROP_SHOW_TABS:
      gtk_notebook_set_show_tabs (notebook, g_value_get_boolean (value));
636
      break;
637
638
    case PROP_SHOW_BORDER:
      gtk_notebook_set_show_border (notebook, g_value_get_boolean (value));
639
      break;
640
641
    case PROP_SCROLLABLE:
      gtk_notebook_set_scrollable (notebook, g_value_get_boolean (value));
642
      break;
643
644
    case PROP_ENABLE_POPUP:
      if (g_value_get_boolean (value))
645
646
647
648
	gtk_notebook_popup_enable (notebook);
      else
	gtk_notebook_popup_disable (notebook);
      break;
649
650
    case PROP_HOMOGENEOUS:
      gtk_notebook_set_homogeneous_tabs (notebook, g_value_get_boolean (value));
651
      break;  
652
    case PROP_PAGE:
653
      gtk_notebook_set_current_page (notebook, g_value_get_int (value));
654
      break;
655
656
    case PROP_TAB_POS:
      gtk_notebook_set_tab_pos (notebook, g_value_get_enum (value));
657
      break;
658
659
    case PROP_TAB_BORDER:
      gtk_notebook_set_tab_border (notebook, g_value_get_uint (value));
660
      break;
661
662
    case PROP_TAB_HBORDER:
      gtk_notebook_set_tab_hborder (notebook, g_value_get_uint (value));
663
      break;
664
665
    case PROP_TAB_VBORDER:
      gtk_notebook_set_tab_vborder (notebook, g_value_get_uint (value));
666
      break;
667
668
669
670
671
672
    default:
      break;
    }
}

static void
673
674
675
676
gtk_notebook_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
677
678
679
680
681
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

682
  switch (prop_id)
683
    {
684
685
    case PROP_SHOW_TABS:
      g_value_set_boolean (value, notebook->show_tabs);
686
      break;
687
688
    case PROP_SHOW_BORDER:
      g_value_set_boolean (value, notebook->show_border);
689
      break;
690
691
    case PROP_SCROLLABLE:
      g_value_set_boolean (value, notebook->scrollable);
692
      break;
693
694
    case PROP_ENABLE_POPUP:
      g_value_set_boolean (value, notebook->menu != NULL);
695
      break;
696
697
    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, notebook->homogeneous);
698
      break;
699
700
    case PROP_PAGE:
      g_value_set_int (value, gtk_notebook_get_current_page (notebook));
701
      break;
702
703
    case PROP_TAB_POS:
      g_value_set_enum (value, notebook->tab_pos);
704
      break;
705
706
    case PROP_TAB_HBORDER:
      g_value_set_uint (value, notebook->tab_hborder);
707
      break;
708
709
    case PROP_TAB_VBORDER:
      g_value_set_uint (value, notebook->tab_vborder);
710
      break;
711
    default:
712
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
713
714
715
716
      break;
    }
}

717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
/* Private GtkWidget Methods :
 * 
 * gtk_notebook_map
 * gtk_notebook_unmap
 * gtk_notebook_realize
 * gtk_notebook_size_request
 * gtk_notebook_size_allocate
 * gtk_notebook_expose
 * gtk_notebook_button_press
 * gtk_notebook_button_release
 * gtk_notebook_enter_notify
 * gtk_notebook_leave_notify
 * gtk_notebook_motion_notify
 * gtk_notebook_focus_in
 * gtk_notebook_focus_out
 * gtk_notebook_draw_focus
 * gtk_notebook_style_set
 */
735
736
737
738
739
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
740
  gint border_width = GTK_CONTAINER (notebook)->border_width;
741
742
743
744
745
746
747

  if (notebook->show_tabs && notebook->children)
    {
      if (rectangle)
	{
	  GtkNotebookPage *page = notebook->children->data;

748
749
	  rectangle->x = widget->allocation.x + border_width;
	  rectangle->y = widget->allocation.y + border_width;
750
751
752
753
754
	  
	  switch (notebook->tab_pos)
	    {
	    case GTK_POS_TOP:
	    case GTK_POS_BOTTOM:
755
	      rectangle->width = widget->allocation.width - 2 * border_width;
756
757
	      rectangle->height = page->requisition.height;
	      if (notebook->tab_pos == GTK_POS_BOTTOM)
758
		rectangle->y += widget->allocation.height - 2 * border_width - rectangle->height;
759
760
761
762
	      break;
	    case GTK_POS_LEFT:
	    case GTK_POS_RIGHT:
	      rectangle->width = page->requisition.width;
763
	      rectangle->height = widget->allocation.height - 2 * border_width;
764
	      if (notebook->tab_pos == GTK_POS_RIGHT)
765
		rectangle->x += widget->allocation.width - 2 * border_width - rectangle->width;
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
	      break;
	    }
	}

      return TRUE;
    }
  else
    {
      if (rectangle)
	{
	  rectangle->x = rectangle->y = 0;
	  rectangle->width = rectangle->height = 10;
	}
    }

  return FALSE;
}

784
static void
785
gtk_notebook_map (GtkWidget *widget)
786
787
{
  GtkNotebook *notebook;
788
789
  GtkNotebookPage *page;
  GList *children;
790

791
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
792

793
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
794

795
  notebook = GTK_NOTEBOOK (widget);
796

797
798
799
800
  if (notebook->cur_page && 
      GTK_WIDGET_VISIBLE (notebook->cur_page->child) &&
      !GTK_WIDGET_MAPPED (notebook->cur_page->child))
    gtk_widget_map (notebook->cur_page->child);
801

802
  if (notebook->scrollable)
803
    gtk_notebook_pages_allocate (notebook);
804
805
806
  else
    {
      children = notebook->children;
807

808
      while (children)
809
	{
810
811
	  page = children->data;
	  children = children->next;
812

Owen Taylor's avatar
Owen Taylor committed
813
814
	  if (page->tab_label &&
	      GTK_WIDGET_VISIBLE (page->tab_label) &&
815
816
	      !GTK_WIDGET_MAPPED (page->tab_label))
	    gtk_widget_map (page->tab_label);
817
	}
818
    }
819

820
  if (gtk_notebook_get_event_window_position (notebook, NULL))
821
    gdk_window_show_unraised (notebook->event_window);
822
}
823

824
825
826
827
static void
gtk_notebook_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
828

829
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
830
831

  gdk_window_hide (GTK_NOTEBOOK (widget)->event_window);
832
833

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
834
835
836
}

static void
837
gtk_notebook_realize (GtkWidget *widget)
838
839
{
  GtkNotebook *notebook;
840
841
  GdkWindowAttr attributes;
  gint attributes_mask;
842
  GdkRectangle event_window_pos;
843

844
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
845

846
847
  notebook = GTK_NOTEBOOK (widget);
  GTK_WIDGET_SET_FLAGS (notebook, GTK_REALIZED);
848

849
850
851
852
853
  gtk_notebook_get_event_window_position (notebook, &event_window_pos);
  
  widget->window = gtk_widget_get_parent_window (widget);
  gdk_window_ref (widget->window);
  
854
  attributes.window_type = GDK_WINDOW_CHILD;
855
856
857
858
859
  attributes.x = event_window_pos.x;
  attributes.y = event_window_pos.y;
  attributes.width = event_window_pos.width;
  attributes.height = event_window_pos.height;
  attributes.wclass = GDK_INPUT_ONLY;
860
861
862
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
			    GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK);
863

864
  attributes_mask = GDK_WA_X | GDK_WA_Y;
865

866
867
868
  notebook->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), 
					   &attributes, attributes_mask);
  gdk_window_set_user_data (notebook->event_window, notebook);
869

870
  widget->style = gtk_style_attach (widget->style, widget->window);
Elliot Lee's avatar
Elliot Lee committed
871
872
}

873
874
875
876
877
878
879
880
881
static void
gtk_notebook_unrealize (GtkWidget *widget)
{
  GtkNotebook *notebook;

  g_return_if_fail (GTK_IS_NOTEBOOK (widget));

  notebook = GTK_NOTEBOOK (widget);

882
883
884
  gdk_window_set_user_data (notebook->event_window, NULL);
  gdk_window_destroy (notebook->event_window);
  notebook->event_window = NULL;
885
886
887
888
889

  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

890
static void
891
892
gtk_notebook_size_request (GtkWidget      *widget,
			   GtkRequisition *requisition)
893
{
894
  GtkNotebook *notebook = GTK_NOTEBOOK (widget);
895
896
  GtkNotebookPage *page;
  GList *children;
897
  GtkRequisition child_requisition;
898
899
  gboolean switch_page = FALSE;
  gint vis_pages;
900
  gint focus_width;
901

902
903
  gtk_widget_style_get (widget, "focus-line-width", &focus_width, NULL);
  
904
905
  widget->requisition.width = 0;
  widget->requisition.height = 0;
906

907
908
909
910
  for (children = notebook->children, vis_pages = 0; children;
       children = children->next)
    {
      page = children->data;
911

912
913
914
      if (GTK_WIDGET_VISIBLE (page->child))
	{
	  vis_pages++;
915
	  gtk_widget_size_request (page->child, &child_requisition);
916
917
	  
	  widget->requisition.width = MAX (widget->requisition.width,
918
					   child_requisition.width);
919
	  widget->requisition.height = MAX (widget->requisition.height,
920
					    child_requisition.height);
Elliot Lee's avatar
Elliot Lee committed
921

922
923
924
925
926
927
928
929
930
931
932
933
934
	  if (notebook->menu && page->menu_label->parent &&
	      !GTK_WIDGET_VISIBLE (page->menu_label->parent))
	    gtk_widget_show (page->menu_label->parent);
	}
      else
	{
	  if (page == notebook->cur_page)
	    switch_page = TRUE;
	  if (notebook->menu && page->menu_label->parent &&
	      GTK_WIDGET_VISIBLE (page->menu_label->parent))
	    gtk_widget_hide (page->menu_label->parent);
	}
    }
935

936
937
  if (notebook->show_border || notebook->show_tabs)
    {
938
939
      widget->requisition.width += widget->style->xthickness * 2;
      widget->requisition.height += widget->style->ythickness * 2;
940

941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
      if (notebook->show_tabs)
	{
	  gint tab_width = 0;
	  gint tab_height = 0;
	  gint tab_max = 0;
	  gint padding;
	  
	  for (children = notebook->children; children;
	       children = children->next)
	    {
	      page = children->data;
	      
	      if (GTK_WIDGET_VISIBLE (page->child))
		{
		  if (!GTK_WIDGET_VISIBLE (page->tab_label))
		    gtk_widget_show (page->tab_label);
Elliot Lee's avatar
Elliot Lee committed
957

958
959
		  gtk_widget_size_request (page->tab_label,
					   &child_requisition);
Elliot Lee's avatar
Elliot Lee committed
960

961
		  page->requisition.width = 
962
		    child_requisition.width +
963
		    2 * widget->style->xthickness;
964
		  page->requisition.height = 
965
		    child_requisition.height +
966
		    2 * widget->style->ythickness;
967
968
969
970
971
972
		  
		  switch (notebook->tab_pos)
		    {
		    case GTK_POS_TOP:
		    case GTK_POS_BOTTOM:
		      page->requisition.height += 2 * (notebook->tab_vborder +
973
						       focus_width);
974
975
976
977
978
979
		      tab_height = MAX (tab_height, page->requisition.height);
		      tab_max = MAX (tab_max, page->requisition.width);
		      break;
		    case GTK_POS_LEFT:
		    case GTK_POS_RIGHT:
		      page->requisition.width += 2 * (notebook->tab_hborder +
980
						      focus_width);
981
982
983
984
985
986
987
988
		      tab_width = MAX (tab_width, page->requisition.width);
		      tab_max = MAX (tab_max, page->requisition.height);
		      break;
		    }
		}
	      else if (GTK_WIDGET_VISIBLE (page->tab_label))
		gtk_widget_hide (page->tab_label);
	    }
989

990
	  children = notebook->children;
991

992
993
994
995
996
997
998
999
	  if (vis_pages)
	    {
	      switch (notebook->tab_pos)
		{
		case GTK_POS_TOP:
		case GTK_POS_BOTTOM:
		  if (tab_height == 0)
		    break;
Elliot Lee's avatar
Elliot Lee committed
1000

1001
1002
1003
		  if (notebook->scrollable && vis_pages > 1 && 
		      widget->requisition.width < tab_width)
		    tab_height = MAX (tab_height, ARROW_SIZE);
1004

1005
		  padding = 2 * (TAB_CURVATURE + focus_width +
1006
1007
1008
1009
1010
1011
1012
1013
1014
				 notebook->tab_hborder) - TAB_OVERLAP;
		  tab_max += padding;
		  while (children)
		    {
		      page = children->data;
		      children = children->next;
		  
		      if (!GTK_WIDGET_VISIBLE (page->child))
			continue;
1015

1016
1017
1018
1019
		      if (notebook->homogeneous)
			page->requisition.width = tab_max;
		      else
			page->requisition.width += padding;
Elliot Lee's avatar
Elliot Lee committed
1020

1021
1022
1023
		      tab_width += page->requisition.width;
		      page->requisition.height = tab_height;
		    }
Elliot Lee's avatar
Elliot Lee committed
1024

1025
1026
1027
		  if (notebook->scrollable && vis_pages > 1 &&
		      widget->requisition.width < tab_width)
		    tab_width = tab_max + 2 * (ARROW_SIZE + ARROW_SPACING);
Elliot Lee's avatar
Elliot Lee committed
1028

1029
1030
1031
1032
1033
1034
1035
                  if (notebook->homogeneous && !notebook->scrollable)
                    widget->requisition.width = MAX (widget->requisition.width,
                                                     vis_pages * tab_max +
                                                     TAB_OVERLAP);
                  else
                    widget->requisition.width = MAX (widget->requisition.width,
                                                     tab_width + TAB_OVERLAP);
Elliot Lee's avatar
Elliot Lee committed
1036

1037
1038
1039
1040
1041
1042
		  widget->requisition.height += tab_height;
		  break;
		case GTK_POS_LEFT:
		case GTK_POS_RIGHT:
		  if (tab_width == 0)
		    break;
1043

1044
1045
1046
		  if (notebook->scrollable && vis_pages > 1 && 
		      widget->requisition.height < tab_height)
		    tab_width = MAX (tab_width, ARROW_SPACING +2 * ARROW_SIZE);
1047

1048
		  padding = 2 * (TAB_CURVATURE + focus_width +
1049
1050
				 notebook->tab_vborder) - TAB_OVERLAP;
		  tab_max += padding;
1051

1052
1053
1054
1055
		  while (children)
		    {
		      page = children->data;
		      children = children->next;
1056

1057
1058
		      if (!GTK_WIDGET_VISIBLE (page->child))
			continue;
1059

1060
		      page->requisition.width   = tab_width;
1061

1062
1063
1064
1065
		      if (notebook->homogeneous)
			page->requisition.height = tab_max;
		      else
			page->requisition.height += padding;
Elliot Lee's avatar
Elliot Lee committed
1066

1067
1068
		      tab_height += page->requisition.height;
		    }
Elliot Lee's avatar
Elliot Lee committed
1069

1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
		  if (notebook->scrollable && vis_pages > 1 && 
		      widget->requisition.height < tab_height)
		    tab_height = tab_max + ARROW_SIZE + ARROW_SPACING;

		  widget->requisition.width += tab_width;

                  if (notebook->homogeneous && !notebook->scrollable)
                    widget->requisition.height =
		      MAX (widget->requisition.height,
			   vis_pages * tab_max + TAB_OVERLAP);
                  else
                    widget->requisition.height =
		      MAX (widget->requisition.height,
			   tab_height + TAB_OVERLAP);

		  if (!notebook->homogeneous || notebook->scrollable)
		    vis_pages = 1;
		  widget->requisition.height = MAX (widget->requisition.height,
						    vis_pages * tab_max +
						    TAB_OVERLAP);
		  break;
		}
	    }
1093
	}
Owen Taylor's avatar
Owen Taylor committed
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
      else
	{
	  for (children = notebook->children; children;
	       children = children->next)
	    {
	      page = children->data;
	      
	      if (page->tab_label && GTK_WIDGET_VISIBLE (page->tab_label))
		gtk_widget_hide (page->tab_label);
	    }
	}
Elliot Lee's avatar
Elliot Lee committed
1105
1106
    }

1107
1108
  widget->requisition.width += GTK_CONTAINER (widget)->border_width * 2;
  widget->requisition.height += GTK_CONTAINER (widget)->border_width * 2;
Elliot Lee's avatar
Elliot Lee committed
1109

1110
  if (switch_page)
1111
    {
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
      if (vis_pages)
	{
	  for (children = notebook->children; children;
	       children = children->next)
	    {
	      page = children->data;
	      if (GTK_WIDGET_VISIBLE (page->child))
		{
		  gtk_notebook_switch_page (notebook, page, -1);
		  break;
		}
	    }
	}
      else if (GTK_WIDGET_VISIBLE (widget))
	{
	  widget->requisition.width = GTK_CONTAINER (widget)->border_width * 2;
	  widget->requisition.height= GTK_CONTAINER (widget)->border_width * 2;
	}
1130
    }
1131
  if (vis_pages && !notebook->cur_page)
1132
    {
1133
1134
1135
1136
1137
1138
      children = gtk_notebook_search_page (notebook, NULL, STEP_NEXT, TRUE);
      if (children)
	{
	  notebook->first_tab = children;
	  gtk_notebook_switch_page (notebook, GTK_NOTEBOOK_PAGE (children),-1);
	}
1139
    }
1140
1141
1142
}

static void
1143
1144
gtk_notebook_size_allocate (GtkWidget     *widget,
			    GtkAllocation *allocation)
1145
{
1146
  GtkNotebook *notebook = GTK_NOTEBOOK (widget);
1147

1148
1149
  widget->allocation = *allocation;
  if (GTK_WIDGET_REALIZED (widget))