gtknotebook.c 139 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
static void gtk_notebook_real_remove         (GtkNotebook      *notebook,
192 193
					      GList            *list,
					      gboolean		destroying);
194 195 196 197
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);
198
static GList* gtk_notebook_find_child        (GtkNotebook      *notebook,
199 200
					      GtkWidget        *child,
					      const gchar      *function);
201 202 203 204 205 206 207
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);

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

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

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

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

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

252 253 254 255 256 257 258 259 260 261
/*** 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);

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

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

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

  if (!notebook_type)
    {
Manish Singh's avatar
Manish Singh committed
276
      static const GTypeInfo notebook_info =
Elliot Lee's avatar
Elliot Lee committed
277 278
      {
	sizeof (GtkNotebookClass),
Manish Singh's avatar
Manish Singh committed
279 280 281 282 283 284 285 286
	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
287 288
      };

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

  return notebook_type;
}

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 323
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
324 325 326
static void
gtk_notebook_class_init (GtkNotebookClass *class)
{
Tim Janik's avatar
Tim Janik committed
327 328 329 330
  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);
331 332
  GtkBindingSet *binding_set;
  
Tim Janik's avatar
Tim Janik committed
333
  parent_class = g_type_class_peek_parent (class);
334

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

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

362
  class->switch_page = gtk_notebook_real_switch_page;
363

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

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

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

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

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

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

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

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

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

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

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
722
  g_signal_emit_by_name (toplevel, "move_focus", direction_type);
723 724 725 726
  notebook->focus_out = FALSE;
  
  g_object_unref (notebook);

727 728
}

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

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

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

757 758 759 760 761 762 763 764 765
  children = notebook->children;
  while (children)
    {
      GList *child = children;
      children = child->next;
      
      gtk_notebook_real_remove (notebook, child, TRUE);
    }
  
766 767 768 769
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

static void
770 771 772 773
gtk_notebook_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
774 775 776 777 778
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

779
  switch (prop_id)
780
    {
781 782
    case PROP_SHOW_TABS:
      gtk_notebook_set_show_tabs (notebook, g_value_get_boolean (value));
783
      break;
784 785
    case PROP_SHOW_BORDER:
      gtk_notebook_set_show_border (notebook, g_value_get_boolean (value));
786
      break;
787 788
    case PROP_SCROLLABLE:
      gtk_notebook_set_scrollable (notebook, g_value_get_boolean (value));
789
      break;
790 791
    case PROP_ENABLE_POPUP:
      if (g_value_get_boolean (value))
792 793 794 795
	gtk_notebook_popup_enable (notebook);
      else
	gtk_notebook_popup_disable (notebook);
      break;
796
    case PROP_HOMOGENEOUS:
797
      gtk_notebook_set_homogeneous_tabs_internal (notebook, g_value_get_boolean (value));
798
      break;  
799
    case PROP_PAGE:
800
      gtk_notebook_set_current_page (notebook, g_value_get_int (value));
801
      break;
802 803
    case PROP_TAB_POS:
      gtk_notebook_set_tab_pos (notebook, g_value_get_enum (value));
804
      break;
805
    case PROP_TAB_BORDER:
806
      gtk_notebook_set_tab_border_internal (notebook, g_value_get_uint (value));
807
      break;
808
    case PROP_TAB_HBORDER:
809
      gtk_notebook_set_tab_hborder_internal (notebook, g_value_get_uint (value));
810
      break;
811
    case PROP_TAB_VBORDER:
812
      gtk_notebook_set_tab_vborder_internal (notebook, g_value_get_uint (value));
813
      break;
814 815 816 817 818 819
    default:
      break;
    }
}

static void
820 821 822 823
gtk_notebook_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
824 825 826 827 828
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

829
  switch (prop_id)
830
    {
831 832
    case PROP_SHOW_TABS:
      g_value_set_boolean (value, notebook->show_tabs);
833
      break;
834 835
    case PROP_SHOW_BORDER:
      g_value_set_boolean (value, notebook->show_border);
836
      break;
837 838
    case PROP_SCROLLABLE:
      g_value_set_boolean (value, notebook->scrollable);
839
      break;
840 841
    case PROP_ENABLE_POPUP:
      g_value_set_boolean (value, notebook->menu != NULL);
842
      break;
843 844
    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, notebook->homogeneous);
845
      break;
846 847
    case PROP_PAGE:
      g_value_set_int (value, gtk_notebook_get_current_page (notebook));
848
      break;
849 850
    case PROP_TAB_POS:
      g_value_set_enum (value, notebook->tab_pos);
851
      break;
852 853
    case PROP_TAB_HBORDER:
      g_value_set_uint (value, notebook->tab_hborder);
854
      break;
855 856
    case PROP_TAB_VBORDER:
      g_value_set_uint (value, notebook->tab_vborder);
857
      break;
858
    default:
859
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
860 861 862 863
      break;
    }
}

864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
/* 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
 */
882 883 884 885 886
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
887
  gint border_width = GTK_CONTAINER (notebook)->border_width;
888 889
  GtkNotebookPage *visible_page = NULL;
  GList *tmp_list;
890

891
  for (tmp_list = notebook->children; tmp_list; tmp_list = tmp_list->next)
892
    {
893 894
      GtkNotebookPage *page = tmp_list->data;
      if (GTK_WIDGET_VISIBLE (page->child))
895
	{
896 897 898 899
	  visible_page = page;
	  break;
	}
    }
900

901 902 903 904
  if (notebook->show_tabs && visible_page)
    {
      if (rectangle)
	{
905 906
	  rectangle->x = widget->allocation.x + border_width;
	  rectangle->y = widget->allocation.y + border_width;
907 908 909 910 911
	  
	  switch (notebook->tab_pos)
	    {
	    case GTK_POS_TOP:
	    case GTK_POS_BOTTOM:
912
	      rectangle->width = widget->allocation.width - 2 * border_width;
913
	      rectangle->height = visible_page->requisition.height;
914
	      if (notebook->tab_pos == GTK_POS_BOTTOM)
915
		rectangle->y += widget->allocation.height - 2 * border_width - rectangle->height;
916 917 918
	      break;
	    case GTK_POS_LEFT:
	    case GTK_POS_RIGHT:
919
	      rectangle->width = visible_page->requisition.width;
920
	      rectangle->height = widget->allocation.height - 2 * border_width;
921
	      if (notebook->tab_pos == GTK_POS_RIGHT)
922
		rectangle->x += widget->allocation.width - 2 * border_width - rectangle->width;
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
	      break;
	    }
	}

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

  return FALSE;
}

941
static void
942
gtk_notebook_map (GtkWidget *widget)
943 944
{
  GtkNotebook *notebook;
945 946
  GtkNotebookPage *page;
  GList *children;
947

948
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
949

950
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
951

952
  notebook = GTK_NOTEBOOK (widget);
953

954 955 956 957
  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);
958

959
  if (notebook->scrollable)
960
    gtk_notebook_pages_allocate (notebook);
961 962 963
  else
    {
      children = notebook->children;
964

965
      while (children)
966
	{
967 968
	  page = children->data;
	  children = children->next;
969

Owen Taylor's avatar
Owen Taylor committed
970 971
	  if (page->tab_label &&
	      GTK_WIDGET_VISIBLE (page->tab_label) &&
972 973
	      !GTK_WIDGET_MAPPED (page->tab_label))
	    gtk_widget_map (page->tab_label);
974
	}
975
    }
976

977
  if (gtk_notebook_get_event_window_position (notebook, NULL))
978
    gdk_window_show_unraised (notebook->event_window);
979
}
980

981 982 983 984
static void
gtk_notebook_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_NOTEBOOK (widget