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 29 30 31 32 33
#include "gtkmain.h"
#include "gtkmenu.h"
#include "gtkmenuitem.h"
#include "gtklabel.h"
#include <gdk/gdkkeysyms.h>
#include <stdio.h>
34
#include "gtkintl.h"
35
#include "gtkmarshalers.h"
36
#include "gtkbindings.h"
Elliot Lee's avatar
Elliot Lee committed
37 38 39 40


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


47 48
enum {
  SWITCH_PAGE,
49 50
  FOCUS_TAB,
  SELECT_PAGE,
51
  CHANGE_CURRENT_PAGE,
52
  MOVE_FOCUS_OUT,
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
#define GTK_NOTEBOOK_PAGE(_glist_)         ((GtkNotebookPage *)((GList *)(_glist_))->data)

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

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

103
  guint mnemonic_activate_signal;
104 105 106 107
};

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

118 119 120 121 122 123 124 125
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);
126

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

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

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

188
/*** GtkNotebook Private Functions ***/
189 190
static void gtk_notebook_redraw_tabs         (GtkNotebook      *notebook);
static void gtk_notebook_redraw_arrows       (GtkNotebook      *notebook);
191 192 193 194 195 196
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

251 252 253 254 255 256 257 258 259 260
/*** GtkNotebook Private Setters ***/
static void gtk_notebook_set_homogeneous_tabs_internal (GtkNotebook *notebook,
							gboolean     homogeneous);
static void gtk_notebook_set_tab_border_internal       (GtkNotebook *notebook,
							guint        border_width);
static void gtk_notebook_set_tab_hborder_internal      (GtkNotebook *notebook,
							guint        tab_hborder);
static void gtk_notebook_set_tab_vborder_internal      (GtkNotebook *notebook,
							guint        tab_vborder);

261 262 263
static gboolean focus_tabs_in  (GtkNotebook      *notebook);
static gboolean focus_child_in (GtkNotebook      *notebook,
				GtkDirectionType  direction);
Elliot Lee's avatar
Elliot Lee committed
264

265
static GtkContainerClass *parent_class = NULL;
266
static guint notebook_signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
267

Manish Singh's avatar
Manish Singh committed
268
GType
269
gtk_notebook_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
270
{
Manish Singh's avatar
Manish Singh committed
271
  static GType notebook_type = 0;
Elliot Lee's avatar
Elliot Lee committed
272 273 274

  if (!notebook_type)
    {
Manish Singh's avatar
Manish Singh committed
275
      static const GTypeInfo notebook_info =
Elliot Lee's avatar
Elliot Lee committed
276 277
      {
	sizeof (GtkNotebookClass),
Manish Singh's avatar
Manish Singh committed
278 279 280 281 282 283 284 285
	NULL,		/* base_init */
	NULL,		/* base_finalize */
	(GClassInitFunc) gtk_notebook_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GtkNotebook),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gtk_notebook_init,
Elliot Lee's avatar
Elliot Lee committed
286 287
      };

Manish Singh's avatar
Manish Singh committed
288 289
      notebook_type = g_type_register_static (GTK_TYPE_CONTAINER, "GtkNotebook",
					      &notebook_info, 0);
Elliot Lee's avatar
Elliot Lee committed
290 291 292 293 294
    }

  return notebook_type;
}

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
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
323 324 325
static void
gtk_notebook_class_init (GtkNotebookClass *class)
{
Tim Janik's avatar
Tim Janik committed
326 327 328 329
  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);
330 331
  GtkBindingSet *binding_set;
  
Tim Janik's avatar
Tim Janik committed
332
  parent_class = g_type_class_peek_parent (class);
333

334 335
  gobject_class->set_property = gtk_notebook_set_property;
  gobject_class->get_property = gtk_notebook_get_property;
336 337
  object_class->destroy = gtk_notebook_destroy;

Elliot Lee's avatar
Elliot Lee committed
338 339 340
  widget_class->map = gtk_notebook_map;
  widget_class->unmap = gtk_notebook_unmap;
  widget_class->realize = gtk_notebook_realize;
341
  widget_class->unrealize = gtk_notebook_unrealize;
Elliot Lee's avatar
Elliot Lee committed
342 343 344 345
  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;
346 347 348 349 350
  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;
351 352
  widget_class->focus = gtk_notebook_focus;
  
Elliot Lee's avatar
Elliot Lee committed
353 354
  container_class->add = gtk_notebook_add;
  container_class->remove = gtk_notebook_remove;
355
  container_class->forall = gtk_notebook_forall;
Lars Hamann's avatar
Lars Hamann committed
356
  container_class->set_focus_child = gtk_notebook_set_focus_child;
Tim Janik's avatar
Tim Janik committed
357 358
  container_class->get_child_property = gtk_notebook_get_child_property;
  container_class->set_child_property = gtk_notebook_set_child_property;
359
  container_class->child_type = gtk_notebook_child_type;
360

361
  class->switch_page = gtk_notebook_real_switch_page;
362

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

  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_LABEL,
450 451 452
					      g_param_spec_string ("tab_label", 
								   _("Tab label"),
								   _("The string displayed on the childs tab label"),
Tim Janik's avatar
Tim Janik committed
453 454 455 456
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_MENU_LABEL,
457 458 459
					      g_param_spec_string ("menu_label", 
								   _("Menu label"), 
								   _("The string displayed in the childs menu entry"),
Tim Janik's avatar
Tim Janik committed
460 461 462 463
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_POSITION,
464 465 466
					      g_param_spec_int ("position", 
								_("Position"), 
								_("The index of the child in the parent"),
Tim Janik's avatar
Tim Janik committed
467 468 469 470
								-1, G_MAXINT, 0,
								G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_EXPAND,
471 472 473
					      g_param_spec_boolean ("tab_expand", 
								    _("Tab expand"), 
								    _("Whether to expand the childs tab or not"),
Tim Janik's avatar
Tim Janik committed
474 475 476 477
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_FILL,
478 479 480
					      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
481 482 483 484
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_PACK,
485 486 487
					      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"),
488 489
								 GTK_TYPE_PACK_TYPE, GTK_PACK_START,
								 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
490
  
491
  notebook_signals[SWITCH_PAGE] =
Manish Singh's avatar
Manish Singh committed
492 493 494 495 496 497 498 499 500
    g_signal_new ("switch_page",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkNotebookClass, switch_page),
		  NULL, NULL,
		  _gtk_marshal_VOID__POINTER_UINT,
		  G_TYPE_NONE, 2,
		  G_TYPE_POINTER,
		  G_TYPE_UINT);
501
  notebook_signals[FOCUS_TAB] = 
502
    g_signal_new ("focus_tab",
Manish Singh's avatar
Manish Singh committed
503
                  G_TYPE_FROM_CLASS (gobject_class),
504 505 506
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, focus_tab),
                  NULL, NULL,
507
                  _gtk_marshal_BOOLEAN__ENUM,
508
                  G_TYPE_BOOLEAN, 1,
509
                  GTK_TYPE_NOTEBOOK_TAB);
510
  notebook_signals[SELECT_PAGE] = 
511
    g_signal_new ("select_page",
Manish Singh's avatar
Manish Singh committed
512
                  G_TYPE_FROM_CLASS (gobject_class),
513 514 515
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, select_page),
                  NULL, NULL,
516 517
                  _gtk_marshal_BOOLEAN__BOOLEAN,
                  G_TYPE_BOOLEAN, 1,
518
                  G_TYPE_BOOLEAN);
519 520
  notebook_signals[CHANGE_CURRENT_PAGE] = 
    g_signal_new ("change_current_page",
Manish Singh's avatar
Manish Singh committed
521
                  G_TYPE_FROM_CLASS (gobject_class),
522 523 524
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkNotebookClass, change_current_page),
                  NULL, NULL,
525 526
                  _gtk_marshal_VOID__INT,
                  G_TYPE_NONE, 1,
527
                  G_TYPE_INT);
528 529
  notebook_signals[MOVE_FOCUS_OUT] =
    g_signal_new ("move_focus_out",
Manish Singh's avatar
Manish Singh committed
530
                  G_TYPE_FROM_CLASS (gobject_class),
531 532 533 534 535 536 537
                  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);
  
538
  
Manish Singh's avatar
Manish Singh committed
539
  binding_set = gtk_binding_set_by_class (class);
540 541 542 543
  gtk_binding_entry_add_signal (binding_set,
                                GDK_space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
544 545 546 547 548
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
  
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
  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);
565 566 567 568 569 570 571 572 573

  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);
574 575 576 577 578 579 580 581

  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
582 583
}

584
static void
585 586
gtk_notebook_init (GtkNotebook *notebook)
{
587
  GTK_WIDGET_SET_FLAGS (notebook, GTK_CAN_FOCUS);
588
  GTK_WIDGET_SET_FLAGS (notebook, GTK_NO_WINDOW);
589 590 591 592 593

  notebook->cur_page = NULL;
  notebook->children = NULL;
  notebook->first_tab = NULL;
  notebook->focus_tab = NULL;
594
  notebook->event_window = NULL;
595 596 597 598 599 600 601 602 603 604 605 606 607 608
  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;
609
  notebook->have_visible_child = FALSE;
610
  notebook->focus_out = FALSE;
611 612
}

613
static gboolean
614 615 616
gtk_notebook_select_page (GtkNotebook *notebook,
                          gboolean     move_focus)
{
617 618 619 620 621 622 623
  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
    {
      gtk_notebook_page_select (notebook, move_focus);
      return TRUE;
    }
  else
    return FALSE;
624 625
}

626
static gboolean
627 628 629 630
gtk_notebook_focus_tab (GtkNotebook       *notebook,
                        GtkNotebookTab     type)
{
  GList *list;
631 632

  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
633
    {
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
      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;
649
    }
650 651
  else
    return FALSE;
652 653
}

654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
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);
671
  else
672
    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
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 716 717 718 719 720
}

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;
Manish Singh's avatar
Manish Singh committed
721
  g_signal_emit_by_name (toplevel, "move_focus", direction_type);
722 723 724 725
  notebook->focus_out = FALSE;
  
  g_object_unref (notebook);

726 727
}

728 729 730 731 732 733 734
/**
 * gtk_notebook_new:
 * 
 * Creates a new #GtkNotebook widget with no pages.

 * Return value: the newly created #GtkNotebook
 **/
735 736 737
GtkWidget*
gtk_notebook_new (void)
{
Manish Singh's avatar
Manish Singh committed
738
  return g_object_new (GTK_TYPE_NOTEBOOK, NULL);
739 740 741 742 743 744 745 746 747 748 749
}

/* Private GtkObject Methods :
 * 
 * gtk_notebook_destroy
 * gtk_notebook_set_arg
 * gtk_notebook_get_arg
 */
static void
gtk_notebook_destroy (GtkObject *object)
{
750
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
751 752 753 754 755 756 757 758
  
  if (notebook->menu)
    gtk_notebook_popup_disable (notebook);

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

static void
759 760 761 762
gtk_notebook_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
763 764 765 766 767
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

768
  switch (prop_id)
769
    {
770 771
    case PROP_SHOW_TABS:
      gtk_notebook_set_show_tabs (notebook, g_value_get_boolean (value));
772
      break;
773 774
    case PROP_SHOW_BORDER:
      gtk_notebook_set_show_border (notebook, g_value_get_boolean (value));
775
      break;
776 777
    case PROP_SCROLLABLE:
      gtk_notebook_set_scrollable (notebook, g_value_get_boolean (value));
778
      break;
779 780
    case PROP_ENABLE_POPUP:
      if (g_value_get_boolean (value))
781 782 783 784
	gtk_notebook_popup_enable (notebook);
      else
	gtk_notebook_popup_disable (notebook);
      break;
785
    case PROP_HOMOGENEOUS:
786
      gtk_notebook_set_homogeneous_tabs_internal (notebook, g_value_get_boolean (value));
787
      break;  
788
    case PROP_PAGE:
789
      gtk_notebook_set_current_page (notebook, g_value_get_int (value));
790
      break;
791 792
    case PROP_TAB_POS:
      gtk_notebook_set_tab_pos (notebook, g_value_get_enum (value));
793
      break;
794
    case PROP_TAB_BORDER:
795
      gtk_notebook_set_tab_border_internal (notebook, g_value_get_uint (value));
796
      break;
797
    case PROP_TAB_HBORDER:
798
      gtk_notebook_set_tab_hborder_internal (notebook, g_value_get_uint (value));
799
      break;
800
    case PROP_TAB_VBORDER:
801
      gtk_notebook_set_tab_vborder_internal (notebook, g_value_get_uint (value));
802
      break;
803 804 805 806 807 808
    default:
      break;
    }
}

static void
809 810 811 812
gtk_notebook_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
813 814 815 816 817
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

818
  switch (prop_id)
819
    {
820 821
    case PROP_SHOW_TABS:
      g_value_set_boolean (value, notebook->show_tabs);
822
      break;
823 824
    case PROP_SHOW_BORDER:
      g_value_set_boolean (value, notebook->show_border);
825
      break;
826 827
    case PROP_SCROLLABLE:
      g_value_set_boolean (value, notebook->scrollable);
828
      break;
829 830
    case PROP_ENABLE_POPUP:
      g_value_set_boolean (value, notebook->menu != NULL);
831
      break;
832 833
    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, notebook->homogeneous);
834
      break;
835 836
    case PROP_PAGE:
      g_value_set_int (value, gtk_notebook_get_current_page (notebook));
837
      break;
838 839
    case PROP_TAB_POS:
      g_value_set_enum (value, notebook->tab_pos);
840
      break;
841 842
    case PROP_TAB_HBORDER:
      g_value_set_uint (value, notebook->tab_hborder);
843
      break;
844 845
    case PROP_TAB_VBORDER:
      g_value_set_uint (value, notebook->tab_vborder);
846
      break;
847
    default:
848
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
849 850 851 852
      break;
    }
}

853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
/* 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
 */
871 872 873 874 875
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
876
  gint border_width = GTK_CONTAINER (notebook)->border_width;
877 878
  GtkNotebookPage *visible_page = NULL;
  GList *tmp_list;
879

880
  for (tmp_list = notebook->children; tmp_list; tmp_list = tmp_list->next)
881
    {
882 883
      GtkNotebookPage *page = tmp_list->data;
      if (GTK_WIDGET_VISIBLE (page->child))
884
	{
885 886 887 888
	  visible_page = page;
	  break;
	}
    }
889

890 891 892 893
  if (notebook->show_tabs && visible_page)
    {
      if (rectangle)
	{
894 895
	  rectangle->x = widget->allocation.x + border_width;
	  rectangle->y = widget->allocation.y + border_width;
896 897 898 899 900
	  
	  switch (notebook->tab_pos)
	    {
	    case GTK_POS_TOP:
	    case GTK_POS_BOTTOM:
901
	      rectangle->width = widget->allocation.width - 2 * border_width;
902
	      rectangle->height = visible_page->requisition.height;
903
	      if (notebook->tab_pos == GTK_POS_BOTTOM)
904
		rectangle->y += widget->allocation.height - 2 * border_width - rectangle->height;
905 906 907
	      break;
	    case GTK_POS_LEFT:
	    case GTK_POS_RIGHT:
908
	      rectangle->width = visible_page->requisition.width;
909
	      rectangle->height = widget->allocation.height - 2 * border_width;
910
	      if (notebook->tab_pos == GTK_POS_RIGHT)
911
		rectangle->x += widget->allocation.width - 2 * border_width - rectangle->width;
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
	      break;
	    }
	}

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

  return FALSE;
}

930
static void
931
gtk_notebook_map (GtkWidget *widget)
932 933
{
  GtkNotebook *notebook;
934 935
  GtkNotebookPage *page;
  GList *children;
936

937
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
938

939
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
940

941
  notebook = GTK_NOTEBOOK (widget);
942

943 944 945 946
  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);
947

948
  if (notebook->scrollable)
949
    gtk_notebook_pages_allocate (notebook);
950 951 952
  else
    {
      children = notebook->children;
953

954
      while (children)
955
	{
956 957
	  page = children->data;
	  children = children->next;
958

Owen Taylor's avatar
Owen Taylor committed
959 960
	  if (page->tab_label &&
	      GTK_WIDGET_VISIBLE (page->tab_label) &&
961 962
	      !GTK_WIDGET_MAPPED (page->tab_label))
	    gtk_widget_map (page->tab_label);
963
	}
964
    }
965

966
  if (gtk_notebook_get_event_window_position (notebook, NULL))
967
    gdk_window_show_unraised (notebook->event_window);
968
}
969

970 971 972 973
static void
gtk_notebook_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
974

975
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
976 977

  gdk_window_hide (GTK_NOTEBOOK (widget)->event_window);
978 979

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
980 981 982
}

static void
983
gtk_notebook_realize (GtkWidget *widget)
984 985
{
  GtkNotebook *notebook;
986 987
  GdkWindowAttr attributes;
  gint attributes_mask;
988
  GdkRectangle event_window_pos;
989

990
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));