gtknotebook.c 138 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
  MOVE_FOCUS_OUT,
54
55
56
  LAST_SIGNAL
};

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

62
enum {
63
64
65
66
67
68
69
70
71
72
73
  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
74
75
76
};

enum {
Tim Janik's avatar
Tim Janik committed
77
78
79
80
81
82
83
  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
84
85
};

86
87
88
89
90
91
92
#define GTK_NOTEBOOK_PAGE(_glist_)         ((GtkNotebookPage *)((GList *)(_glist_))->data)

struct _GtkNotebookPage
{
  GtkWidget *child;
  GtkWidget *tab_label;
  GtkWidget *menu_label;
93
  GtkWidget *last_focus_child;	/* Last descendant of the page that had focus */
94
95
96
97
98
99
100
101
102

  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;
103

104
  guint mnemonic_activate_signal;
105
106
107
108
};

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

119
120
121
122
123
124
125
126
static gboolean gtk_notebook_select_page         (GtkNotebook      *notebook,
						  gboolean          move_focus);
static gboolean gtk_notebook_focus_tab           (GtkNotebook      *notebook,
						  GtkNotebookTab    type);
static void     gtk_notebook_change_current_page (GtkNotebook      *notebook,
						  gint              offset);
static void     gtk_notebook_move_focus_out      (GtkNotebook      *notebook,
						  GtkDirectionType  direction_type);
127

128
/*** GtkObject Methods ***/
129
static void gtk_notebook_destroy             (GtkObject        *object);
130
131
132
133
134
135
136
137
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);
138

139
/*** GtkWidget Methods ***/
140
141
142
static void gtk_notebook_map                 (GtkWidget        *widget);
static void gtk_notebook_unmap               (GtkWidget        *widget);
static void gtk_notebook_realize             (GtkWidget        *widget);
143
static void gtk_notebook_unrealize           (GtkWidget        *widget);
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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);
160
161
162
static gint gtk_notebook_focus_in            (GtkWidget        *widget,
					      GdkEventFocus    *event);
static void gtk_notebook_draw_focus          (GtkWidget        *widget);
163
164
static gint gtk_notebook_focus               (GtkWidget        *widget,
					      GtkDirectionType  direction);
165

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

189
/*** GtkNotebook Private Functions ***/
190
191
static void gtk_notebook_redraw_tabs         (GtkNotebook      *notebook);
static void gtk_notebook_redraw_arrows       (GtkNotebook      *notebook);
192
193
194
195
196
197
198
199
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);
200
static GList* gtk_notebook_find_child        (GtkNotebook      *notebook,
201
202
					      GtkWidget        *child,
					      const gchar      *function);
203
204
205
206
207
208
209
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);

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

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

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

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

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

254
255
256
static gboolean focus_tabs_in  (GtkNotebook      *notebook);
static gboolean focus_child_in (GtkNotebook      *notebook,
				GtkDirectionType  direction);
Elliot Lee's avatar
Elliot Lee committed
257

258
static GtkContainerClass *parent_class = NULL;
259
static guint notebook_signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
260

261
GtkType
262
gtk_notebook_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
263
{
264
  static GtkType notebook_type = 0;
Elliot Lee's avatar
Elliot Lee committed
265
266
267

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

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

  return notebook_type;
}

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
static void
add_tab_bindings (GtkBindingSet    *binding_set,
		  GdkModifierType   modifiers,
		  GtkDirectionType  direction)
{
  gtk_binding_entry_add_signal (binding_set, GDK_Tab, modifiers,
                                "move_focus_out", 1,
                                GTK_TYPE_DIRECTION_TYPE, direction);
  gtk_binding_entry_add_signal (binding_set, GDK_KP_Tab, modifiers,
                                "move_focus_out", 1,
                                GTK_TYPE_DIRECTION_TYPE, direction);
}

static void
add_arrow_bindings (GtkBindingSet    *binding_set,
		    guint             keysym,
		    GtkDirectionType  direction)
{
  guint keypad_keysym = keysym - GDK_Left + GDK_KP_Left;
  
  gtk_binding_entry_add_signal (binding_set, keysym, GDK_CONTROL_MASK,
                                "move_focus_out", 1,
                                GTK_TYPE_DIRECTION_TYPE, direction);
  gtk_binding_entry_add_signal (binding_set, keypad_keysym, GDK_CONTROL_MASK,
                                "move_focus_out", 1,
                                GTK_TYPE_DIRECTION_TYPE, direction);
}

Elliot Lee's avatar
Elliot Lee committed
314
315
316
static void
gtk_notebook_class_init (GtkNotebookClass *class)
{
Tim Janik's avatar
Tim Janik committed
317
318
319
320
  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);
321
322
  GtkBindingSet *binding_set;
  
Tim Janik's avatar
Tim Janik committed
323
  parent_class = g_type_class_peek_parent (class);
324

325
326
  gobject_class->set_property = gtk_notebook_set_property;
  gobject_class->get_property = gtk_notebook_get_property;
327
328
  object_class->destroy = gtk_notebook_destroy;

Elliot Lee's avatar
Elliot Lee committed
329
330
331
  widget_class->map = gtk_notebook_map;
  widget_class->unmap = gtk_notebook_unmap;
  widget_class->realize = gtk_notebook_realize;
332
  widget_class->unrealize = gtk_notebook_unrealize;
Elliot Lee's avatar
Elliot Lee committed
333
334
335
336
  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;
337
338
339
340
341
  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;
342
343
  widget_class->focus = gtk_notebook_focus;
  
Elliot Lee's avatar
Elliot Lee committed
344
345
  container_class->add = gtk_notebook_add;
  container_class->remove = gtk_notebook_remove;
346
  container_class->forall = gtk_notebook_forall;
Lars Hamann's avatar
Lars Hamann committed
347
  container_class->set_focus_child = gtk_notebook_set_focus_child;
Tim Janik's avatar
Tim Janik committed
348
349
  container_class->get_child_property = gtk_notebook_get_child_property;
  container_class->set_child_property = gtk_notebook_set_child_property;
350
  container_class->child_type = gtk_notebook_child_type;
351

352
  class->switch_page = gtk_notebook_real_switch_page;
353

354
355
  class->focus_tab = gtk_notebook_focus_tab;
  class->select_page = gtk_notebook_select_page;
356
  class->change_current_page = gtk_notebook_change_current_page;
357
  class->move_focus_out = gtk_notebook_move_focus_out;
358
  
359
360
361
362
363
364
365
366
367
  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
368
369
370
  g_object_class_install_property (gobject_class,
				   PROP_TAB_POS,
				   g_param_spec_enum ("tab_pos",
371
372
373
374
375
 						      _("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
376
377
378
  g_object_class_install_property (gobject_class,
				   PROP_TAB_BORDER,
				   g_param_spec_uint ("tab_border",
379
380
381
382
383
384
 						      _("Tab Border"),
 						      _("Width of the border around the tab labels"),
 						      0,
 						      G_MAXUINT,
 						      2,
 						      G_PARAM_WRITABLE));
Tim Janik's avatar
Tim Janik committed
385
386
387
  g_object_class_install_property (gobject_class,
				   PROP_TAB_HBORDER,
				   g_param_spec_uint ("tab_hborder",
388
389
390
391
392
393
 						      _("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
394
395
396
  g_object_class_install_property (gobject_class,
				   PROP_TAB_VBORDER,
				   g_param_spec_uint ("tab_vborder",
397
398
399
400
401
402
 						      _("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
403
404
405
  g_object_class_install_property (gobject_class,
				   PROP_SHOW_TABS,
				   g_param_spec_boolean ("show_tabs",
406
407
408
409
 							 _("Show Tabs"),
 							 _("Whether tabs should be shown or not"),
 							 TRUE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
410
411
412
  g_object_class_install_property (gobject_class,
				   PROP_SHOW_BORDER,
				   g_param_spec_boolean ("show_border",
413
414
415
416
 							 _("Show Border"),
 							 _("Whether the border should be shown or not"),
 							 TRUE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
417
418
419
  g_object_class_install_property (gobject_class,
				   PROP_SCROLLABLE,
				   g_param_spec_boolean ("scrollable",
420
 							 _("Scrollable"),
Matthias Clasen's avatar
Matthias Clasen committed
421
 							 _("If TRUE, scroll arrows are added if there are too many tabs to fit"),
422
423
 							 FALSE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
424
425
426
  g_object_class_install_property (gobject_class,
				   PROP_ENABLE_POPUP,
				   g_param_spec_boolean ("enable_popup",
427
428
429
430
 							 _("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
431
432
433
  g_object_class_install_property (gobject_class,
				   PROP_HOMOGENEOUS,
				   g_param_spec_boolean ("homogeneous",
434
435
436
 							 _("Homogeneous"),
 							 _("Whether tabs should have homogeneous sizes"),
 							 FALSE,
Tim Janik's avatar
Tim Janik committed
437
438
439
440
							 G_PARAM_READWRITE));

  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_LABEL,
441
442
443
					      g_param_spec_string ("tab_label", 
								   _("Tab label"),
								   _("The string displayed on the childs tab label"),
Tim Janik's avatar
Tim Janik committed
444
445
446
447
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_MENU_LABEL,
448
449
450
					      g_param_spec_string ("menu_label", 
								   _("Menu label"), 
								   _("The string displayed in the childs menu entry"),
Tim Janik's avatar
Tim Janik committed
451
452
453
454
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_POSITION,
455
456
457
					      g_param_spec_int ("position", 
								_("Position"), 
								_("The index of the child in the parent"),
Tim Janik's avatar
Tim Janik committed
458
459
460
461
								-1, G_MAXINT, 0,
								G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_EXPAND,
462
463
464
					      g_param_spec_boolean ("tab_expand", 
								    _("Tab expand"), 
								    _("Whether to expand the childs tab or not"),
Tim Janik's avatar
Tim Janik committed
465
466
467
468
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_FILL,
469
470
471
					      g_param_spec_boolean ("tab_fill", 
								    _("Tab fill"), 
								    _("Wheather the childs tab should fill the allocated area or not"),
Tim Janik's avatar
Tim Janik committed
472
473
474
475
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_PACK,
476
477
478
					      g_param_spec_enum ("tab_pack", 
								 _("Tab pack type"),
								 _("A GtkPackType indicating whether the child is packed with reference to the start or end of the parent"),
479
480
								 GTK_TYPE_PACK_TYPE, GTK_PACK_START,
								 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
481
  
482
483
484
485
486
  notebook_signals[SWITCH_PAGE] =
    gtk_signal_new ("switch_page",
		    GTK_RUN_LAST,
		    GTK_CLASS_TYPE (object_class),
		    GTK_SIGNAL_OFFSET (GtkNotebookClass, switch_page),
487
		    _gtk_marshal_VOID__POINTER_UINT,
488
489
490
		    GTK_TYPE_NONE, 2,
		    GTK_TYPE_POINTER,
		    GTK_TYPE_UINT);
491
  notebook_signals[FOCUS_TAB] = 
492
493
494
495
496
    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,
497
                  _gtk_marshal_BOOLEAN__ENUM,
498
                  G_TYPE_BOOLEAN, 1,
499
                  GTK_TYPE_NOTEBOOK_TAB);
500
  notebook_signals[SELECT_PAGE] = 
501
502
503
504
505
    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,
506
507
                  _gtk_marshal_BOOLEAN__BOOLEAN,
                  G_TYPE_BOOLEAN, 1,
508
                  G_TYPE_BOOLEAN);
509
510
511
512
513
514
  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,
515
516
                  _gtk_marshal_VOID__INT,
                  G_TYPE_NONE, 1,
517
                  G_TYPE_INT);
518
519
520
521
522
523
524
525
526
527
  notebook_signals[MOVE_FOCUS_OUT] =
    g_signal_new ("move_focus_out",
                  G_TYPE_FROM_CLASS (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, move_focus_out),
                  NULL, NULL,
                  _gtk_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1,
                  GTK_TYPE_DIRECTION_TYPE);
  
528
  
529
530
531
532
533
  binding_set = gtk_binding_set_by_class (object_class);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
534
535
536
537
538
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
  
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
  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);
555
556
557
558
559
560
561
562
563

  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);
564
565
566
567
568
569
570
571

  add_arrow_bindings (binding_set, GDK_Up, GTK_DIR_UP);
  add_arrow_bindings (binding_set, GDK_Down, GTK_DIR_DOWN);
  add_arrow_bindings (binding_set, GDK_Left, GTK_DIR_LEFT);
  add_arrow_bindings (binding_set, GDK_Right, GTK_DIR_RIGHT);

  add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
  add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
Elliot Lee's avatar
Elliot Lee committed
572
573
}

574
static void
575
576
gtk_notebook_init (GtkNotebook *notebook)
{
577
  GTK_WIDGET_SET_FLAGS (notebook, GTK_CAN_FOCUS | GTK_RECEIVES_DEFAULT);
578
  GTK_WIDGET_SET_FLAGS (notebook, GTK_NO_WINDOW);
579
580
581
582
583

  notebook->cur_page = NULL;
  notebook->children = NULL;
  notebook->first_tab = NULL;
  notebook->focus_tab = NULL;
584
  notebook->event_window = NULL;
585
586
587
588
589
590
591
592
593
594
595
596
597
598
  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;
599
  notebook->have_visible_child = FALSE;
600
  notebook->focus_out = FALSE;
601
602
}

603
static gboolean
604
605
606
gtk_notebook_select_page (GtkNotebook *notebook,
                          gboolean     move_focus)
{
607
608
609
610
611
612
613
  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
    {
      gtk_notebook_page_select (notebook, move_focus);
      return TRUE;
    }
  else
    return FALSE;
614
615
}

616
static gboolean
617
618
619
620
gtk_notebook_focus_tab (GtkNotebook       *notebook,
                        GtkNotebookTab     type)
{
  GList *list;
621
622

  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
623
    {
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
      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;
	}

      return TRUE;
639
    }
640
641
  else
    return FALSE;
642
643
}

644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
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);
661
  else
662
    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
}

static GtkDirectionType
get_effective_direction (GtkNotebook      *notebook,
			 GtkDirectionType  direction)
{
  /* Remap the directions into the effective direction it would be for a
   * GTK_POS_TOP notebook
   */
#define D(rest) GTK_DIR_##rest

  static const GtkDirectionType translate_direction[4][6] = {
    /* LEFT */   { D(TAB_FORWARD),  D(TAB_BACKWARD), D(LEFT), D(RIGHT), D(UP),   D(DOWN) },
    /* RIGHT */  { D(TAB_BACKWARD), D(TAB_FORWARD),  D(LEFT), D(RIGHT), D(DOWN), D(UP)   },
    /* TOP */    { D(TAB_FORWARD),  D(TAB_BACKWARD), D(UP),   D(DOWN),  D(LEFT), D(RIGHT) },
    /* BOTTOM */ { D(TAB_BACKWARD), D(TAB_FORWARD),  D(DOWN), D(UP),    D(LEFT), D(RIGHT) },
  };

#undef D

  return translate_direction[notebook->tab_pos][direction];
}

static void
gtk_notebook_move_focus_out (GtkNotebook      *notebook,
			     GtkDirectionType  direction_type)
{
  GtkDirectionType effective_direction = get_effective_direction (notebook, direction_type);
  GtkWidget *toplevel;
  
  if (GTK_CONTAINER (notebook)->focus_child && effective_direction == GTK_DIR_UP)
    if (focus_tabs_in (notebook))
      return;
  
  if (gtk_widget_is_focus (GTK_WIDGET (notebook)) && effective_direction == GTK_DIR_DOWN)
    if (focus_child_in (notebook, GTK_DIR_TAB_FORWARD))
      return;

  /* At this point, we know we should be focusing out of the notebook entirely. We
   * do this by setting a flag, then propagating the focus motion to the notebook.
   */
  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (notebook));
  if (!GTK_WIDGET_TOPLEVEL (toplevel))
    return;

  g_object_ref (notebook);
  
  notebook->focus_out = TRUE;
  g_signal_emit_by_name (G_OBJECT (toplevel), "move_focus", direction_type);
  notebook->focus_out = FALSE;
  
  g_object_unref (notebook);

716
717
}

718
719
720
721
722
723
724
/**
 * gtk_notebook_new:
 * 
 * Creates a new #GtkNotebook widget with no pages.

 * Return value: the newly created #GtkNotebook
 **/
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
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)
{
740
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
741
742
743
744
745
746
747
748
  
  if (notebook->menu)
    gtk_notebook_popup_disable (notebook);

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

static void
749
750
751
752
gtk_notebook_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
753
754
755
756
757
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

758
  switch (prop_id)
759
    {
760
761
    case PROP_SHOW_TABS:
      gtk_notebook_set_show_tabs (notebook, g_value_get_boolean (value));
762
      break;
763
764
    case PROP_SHOW_BORDER:
      gtk_notebook_set_show_border (notebook, g_value_get_boolean (value));
765
      break;
766
767
    case PROP_SCROLLABLE:
      gtk_notebook_set_scrollable (notebook, g_value_get_boolean (value));
768
      break;
769
770
    case PROP_ENABLE_POPUP:
      if (g_value_get_boolean (value))
771
772
773
774
	gtk_notebook_popup_enable (notebook);
      else
	gtk_notebook_popup_disable (notebook);
      break;
775
776
    case PROP_HOMOGENEOUS:
      gtk_notebook_set_homogeneous_tabs (notebook, g_value_get_boolean (value));
777
      break;  
778
    case PROP_PAGE:
779
      gtk_notebook_set_current_page (notebook, g_value_get_int (value));
780
      break;
781
782
    case PROP_TAB_POS:
      gtk_notebook_set_tab_pos (notebook, g_value_get_enum (value));
783
      break;
784
785
    case PROP_TAB_BORDER:
      gtk_notebook_set_tab_border (notebook, g_value_get_uint (value));
786
      break;
787
788
    case PROP_TAB_HBORDER:
      gtk_notebook_set_tab_hborder (notebook, g_value_get_uint (value));
789
      break;
790
791
    case PROP_TAB_VBORDER:
      gtk_notebook_set_tab_vborder (notebook, g_value_get_uint (value));
792
      break;
793
794
795
796
797
798
    default:
      break;
    }
}

static void
799
800
801
802
gtk_notebook_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
803
804
805
806
807
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

808
  switch (prop_id)
809
    {
810
811
    case PROP_SHOW_TABS:
      g_value_set_boolean (value, notebook->show_tabs);
812
      break;
813
814
    case PROP_SHOW_BORDER:
      g_value_set_boolean (value, notebook->show_border);
815
      break;
816
817
    case PROP_SCROLLABLE:
      g_value_set_boolean (value, notebook->scrollable);
818
      break;
819
820
    case PROP_ENABLE_POPUP:
      g_value_set_boolean (value, notebook->menu != NULL);
821
      break;
822
823
    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, notebook->homogeneous);
824
      break;
825
826
    case PROP_PAGE:
      g_value_set_int (value, gtk_notebook_get_current_page (notebook));
827
      break;
828
829
    case PROP_TAB_POS:
      g_value_set_enum (value, notebook->tab_pos);
830
      break;
831
832
    case PROP_TAB_HBORDER:
      g_value_set_uint (value, notebook->tab_hborder);
833
      break;
834
835
    case PROP_TAB_VBORDER:
      g_value_set_uint (value, notebook->tab_vborder);
836
      break;
837
    default:
838
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
839
840
841
842
      break;
    }
}

843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
/* 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
 */
861
862
863
864
865
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
866
  gint border_width = GTK_CONTAINER (notebook)->border_width;
867
868
  GtkNotebookPage *visible_page = NULL;
  GList *tmp_list;
869

870
  for (tmp_list = notebook->children; tmp_list; tmp_list = tmp_list->next)
871
    {
872
873
      GtkNotebookPage *page = tmp_list->data;
      if (GTK_WIDGET_VISIBLE (page->child))
874
	{
875
876
877
878
	  visible_page = page;
	  break;
	}
    }
879

880
881
882
883
  if (notebook->show_tabs && visible_page)
    {
      if (rectangle)
	{
884
885
	  rectangle->x = widget->allocation.x + border_width;
	  rectangle->y = widget->allocation.y + border_width;
886
887
888
889
890
	  
	  switch (notebook->tab_pos)
	    {
	    case GTK_POS_TOP:
	    case GTK_POS_BOTTOM:
891
	      rectangle->width = widget->allocation.width - 2 * border_width;
892
	      rectangle->height = visible_page->requisition.height;
893
	      if (notebook->tab_pos == GTK_POS_BOTTOM)
894
		rectangle->y += widget->allocation.height - 2 * border_width - rectangle->height;
895
896
897
	      break;
	    case GTK_POS_LEFT:
	    case GTK_POS_RIGHT:
898
	      rectangle->width = visible_page->requisition.width;
899
	      rectangle->height = widget->allocation.height - 2 * border_width;
900
	      if (notebook->tab_pos == GTK_POS_RIGHT)
901
		rectangle->x += widget->allocation.width - 2 * border_width - rectangle->width;
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
	      break;
	    }
	}

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

  return FALSE;
}

920
static void
921
gtk_notebook_map (GtkWidget *widget)
922
923
{
  GtkNotebook *notebook;
924
925
  GtkNotebookPage *page;
  GList *children;
926

927
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
928

929
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
930

931
  notebook = GTK_NOTEBOOK (widget);
932

933
934
935
936
  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);
937

938
  if (notebook->scrollable)
939
    gtk_notebook_pages_allocate (notebook);
940
941
942
  else
    {
      children = notebook->children;
943

944
      while (children)
945
	{
946
947
	  page = children->data;
	  children = children->next;
948

Owen Taylor's avatar
Owen Taylor committed
949
950
	  if (page->tab_label &&
	      GTK_WIDGET_VISIBLE (page->tab_label) &&
951
952
	      !GTK_WIDGET_MAPPED (page->tab_label))
	    gtk_widget_map (page->tab_label);
953
	}
954
    }
955

956
  if (gtk_notebook_get_event_window_position (notebook, NULL))
957
    gdk_window_show_unraised (notebook->event_window);
958
}
959

960
961
962
963
static void
gtk_notebook_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
964

965
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
966
967

  gdk_window_hide (GTK_NOTEBOOK (widget)->event_window);
968
969

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
970
971
972
}

static void
973
gtk_notebook_realize (GtkWidget *widget)
974
975
{
  GtkNotebook *notebook;
976
977
  GdkWindowAttr attributes;
  gint attributes_mask;
978
  GdkRectangle event_window_pos;
979

980
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
981

982
983
  notebook = GTK_NOTEBOOK (widget);
  GTK_WIDGET_SET_FLAGS (notebook, GTK_REALIZED);
984

985
986
987
988
989
  gtk_notebook_get_event_window_position (notebook, &event_window_pos);
  
  widget->window = gtk_widget_get_parent_window (widget);
  gdk_window_ref (widget->window);
  
990
  attributes.window_type = GDK_WINDOW_CHILD;
991
992
993
994
995
  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;
996
997
998
  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);
999

1000
  attributes_mask = GDK_WA_X | GDK_WA_Y;