gtknotebook.c 137 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 421 422 423
 							 _("Scrollable"),
 							 _("If TRUE, scroll arrows are added if there are to many tabs to fit"),
 							 FALSE,
 							 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
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 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
							 G_PARAM_READWRITE));

  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_LABEL,
					      g_param_spec_string ("tab_label", NULL, NULL,
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_MENU_LABEL,
					      g_param_spec_string ("menu_label", NULL, NULL,
								   NULL,
								   G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_POSITION,
					      g_param_spec_int ("position", NULL, NULL,
								-1, G_MAXINT, 0,
								G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_EXPAND,
					      g_param_spec_boolean ("tab_expand", NULL, NULL,
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_FILL,
					      g_param_spec_boolean ("tab_fill", NULL, NULL,
								    TRUE,
								    G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
					      CHILD_PROP_TAB_PACK,
466 467 468
					      g_param_spec_enum ("tab_pack", NULL, NULL,
								 GTK_TYPE_PACK_TYPE, GTK_PACK_START,
								 G_PARAM_READWRITE));
Tim Janik's avatar
Tim Janik committed
469
  
470 471 472 473 474
  notebook_signals[SWITCH_PAGE] =
    gtk_signal_new ("switch_page",
		    GTK_RUN_LAST,
		    GTK_CLASS_TYPE (object_class),
		    GTK_SIGNAL_OFFSET (GtkNotebookClass, switch_page),
475
		    _gtk_marshal_VOID__POINTER_UINT,
476 477 478
		    GTK_TYPE_NONE, 2,
		    GTK_TYPE_POINTER,
		    GTK_TYPE_UINT);
479
  notebook_signals[FOCUS_TAB] = 
480 481 482 483 484
    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,
485
                  _gtk_marshal_BOOLEAN__ENUM,
486
                  G_TYPE_BOOLEAN, 1,
487
                  GTK_TYPE_NOTEBOOK_TAB);
488
  notebook_signals[SELECT_PAGE] = 
489 490 491 492 493
    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,
494 495
                  _gtk_marshal_BOOLEAN__BOOLEAN,
                  G_TYPE_BOOLEAN, 1,
496
                  G_TYPE_BOOLEAN);
497 498 499 500 501 502
  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,
503 504
                  _gtk_marshal_VOID__INT,
                  G_TYPE_NONE, 1,
505
                  G_TYPE_INT);
506 507 508 509 510 511 512 513 514 515
  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);
  
516
  
517 518 519 520 521
  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);
522 523 524 525 526
  gtk_binding_entry_add_signal (binding_set,
                                GDK_KP_Space, 0,
                                "select_page", 1, 
                                G_TYPE_BOOLEAN, FALSE);
  
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
  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);
543 544 545 546 547 548 549 550 551

  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);
552 553 554 555 556 557 558 559

  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
560 561
}

562
static void
563 564
gtk_notebook_init (GtkNotebook *notebook)
{
565
  GTK_WIDGET_SET_FLAGS (notebook, GTK_CAN_FOCUS | GTK_RECEIVES_DEFAULT);
566
  GTK_WIDGET_SET_FLAGS (notebook, GTK_NO_WINDOW);
567 568 569 570 571

  notebook->cur_page = NULL;
  notebook->children = NULL;
  notebook->first_tab = NULL;
  notebook->focus_tab = NULL;
572
  notebook->event_window = NULL;
573 574 575 576 577 578 579 580 581 582 583 584 585 586
  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;
587
  notebook->have_visible_child = FALSE;
588
  notebook->focus_out = FALSE;
589 590
}

591
static gboolean
592 593 594
gtk_notebook_select_page (GtkNotebook *notebook,
                          gboolean     move_focus)
{
595 596 597 598 599 600 601
  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
    {
      gtk_notebook_page_select (notebook, move_focus);
      return TRUE;
    }
  else
    return FALSE;
602 603
}

604
static gboolean
605 606 607 608
gtk_notebook_focus_tab (GtkNotebook       *notebook,
                        GtkNotebookTab     type)
{
  GList *list;
609 610

  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
611
    {
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
      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;
627
    }
628 629
  else
    return FALSE;
630 631
}

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
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);
649 650 651 652 653 654 655 656 657 658 659 660 661 662 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
  else
    gdk_beep ();
}

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

704 705
}

706 707 708 709 710 711 712
/**
 * gtk_notebook_new:
 * 
 * Creates a new #GtkNotebook widget with no pages.

 * Return value: the newly created #GtkNotebook
 **/
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
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)
{
728
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
729 730 731 732 733 734 735 736
  
  if (notebook->menu)
    gtk_notebook_popup_disable (notebook);

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

static void
737 738 739 740
gtk_notebook_set_property (GObject         *object,
			   guint            prop_id,
			   const GValue    *value,
			   GParamSpec      *pspec)
741 742 743 744 745
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

746
  switch (prop_id)
747
    {
748 749
    case PROP_SHOW_TABS:
      gtk_notebook_set_show_tabs (notebook, g_value_get_boolean (value));
750
      break;
751 752
    case PROP_SHOW_BORDER:
      gtk_notebook_set_show_border (notebook, g_value_get_boolean (value));
753
      break;
754 755
    case PROP_SCROLLABLE:
      gtk_notebook_set_scrollable (notebook, g_value_get_boolean (value));
756
      break;
757 758
    case PROP_ENABLE_POPUP:
      if (g_value_get_boolean (value))
759 760 761 762
	gtk_notebook_popup_enable (notebook);
      else
	gtk_notebook_popup_disable (notebook);
      break;
763 764
    case PROP_HOMOGENEOUS:
      gtk_notebook_set_homogeneous_tabs (notebook, g_value_get_boolean (value));
765
      break;  
766
    case PROP_PAGE:
767
      gtk_notebook_set_current_page (notebook, g_value_get_int (value));
768
      break;
769 770
    case PROP_TAB_POS:
      gtk_notebook_set_tab_pos (notebook, g_value_get_enum (value));
771
      break;
772 773
    case PROP_TAB_BORDER:
      gtk_notebook_set_tab_border (notebook, g_value_get_uint (value));
774
      break;
775 776
    case PROP_TAB_HBORDER:
      gtk_notebook_set_tab_hborder (notebook, g_value_get_uint (value));
777
      break;
778 779
    case PROP_TAB_VBORDER:
      gtk_notebook_set_tab_vborder (notebook, g_value_get_uint (value));
780
      break;
781 782 783 784 785 786
    default:
      break;
    }
}

static void
787 788 789 790
gtk_notebook_get_property (GObject         *object,
			   guint            prop_id,
			   GValue          *value,
			   GParamSpec      *pspec)
791 792 793 794 795
{
  GtkNotebook *notebook;

  notebook = GTK_NOTEBOOK (object);

796
  switch (prop_id)
797
    {
798 799
    case PROP_SHOW_TABS:
      g_value_set_boolean (value, notebook->show_tabs);
800
      break;
801 802
    case PROP_SHOW_BORDER:
      g_value_set_boolean (value, notebook->show_border);
803
      break;
804 805
    case PROP_SCROLLABLE:
      g_value_set_boolean (value, notebook->scrollable);
806
      break;
807 808
    case PROP_ENABLE_POPUP:
      g_value_set_boolean (value, notebook->menu != NULL);
809
      break;
810 811
    case PROP_HOMOGENEOUS:
      g_value_set_boolean (value, notebook->homogeneous);
812
      break;
813 814
    case PROP_PAGE:
      g_value_set_int (value, gtk_notebook_get_current_page (notebook));
815
      break;
816 817
    case PROP_TAB_POS:
      g_value_set_enum (value, notebook->tab_pos);
818
      break;
819 820
    case PROP_TAB_HBORDER:
      g_value_set_uint (value, notebook->tab_hborder);
821
      break;
822 823
    case PROP_TAB_VBORDER:
      g_value_set_uint (value, notebook->tab_vborder);
824
      break;
825
    default:
826
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
827 828 829 830
      break;
    }
}

831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
/* 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
 */
849 850 851 852 853
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
854
  gint border_width = GTK_CONTAINER (notebook)->border_width;
855 856 857 858 859 860 861

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

862 863
	  rectangle->x = widget->allocation.x + border_width;
	  rectangle->y = widget->allocation.y + border_width;
864 865 866 867 868
	  
	  switch (notebook->tab_pos)
	    {
	    case GTK_POS_TOP:
	    case GTK_POS_BOTTOM:
869
	      rectangle->width = widget->allocation.width - 2 * border_width;
870 871
	      rectangle->height = page->requisition.height;
	      if (notebook->tab_pos == GTK_POS_BOTTOM)
872
		rectangle->y += widget->allocation.height - 2 * border_width - rectangle->height;
873 874 875 876
	      break;
	    case GTK_POS_LEFT:
	    case GTK_POS_RIGHT:
	      rectangle->width = page->requisition.width;
877
	      rectangle->height = widget->allocation.height - 2 * border_width;
878
	      if (notebook->tab_pos == GTK_POS_RIGHT)
879
		rectangle->x += widget->allocation.width - 2 * border_width - rectangle->width;
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
	      break;
	    }
	}

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

  return FALSE;
}

898
static void
899
gtk_notebook_map (GtkWidget *widget)
900 901
{
  GtkNotebook *notebook;
902 903
  GtkNotebookPage *page;
  GList *children;
904

905
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
906

907
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
908

909
  notebook = GTK_NOTEBOOK (widget);
910

911 912 913 914
  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);
915

916
  if (notebook->scrollable)
917
    gtk_notebook_pages_allocate (notebook);
918 919 920
  else
    {
      children = notebook->children;
921

922
      while (children)
923
	{
924 925
	  page = children->data;
	  children = children->next;
926

Owen Taylor's avatar
Owen Taylor committed
927 928
	  if (page->tab_label &&
	      GTK_WIDGET_VISIBLE (page->tab_label) &&
929 930
	      !GTK_WIDGET_MAPPED (page->tab_label))
	    gtk_widget_map (page->tab_label);
931
	}
932
    }
933

934
  if (gtk_notebook_get_event_window_position (notebook, NULL))
935
    gdk_window_show_unraised (notebook->event_window);
936
}
937

938 939 940 941
static void
gtk_notebook_unmap (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
942

943
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
944 945

  gdk_window_hide (GTK_NOTEBOOK (widget)->event_window);
946 947

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
948 949 950
}

static void
951
gtk_notebook_realize (GtkWidget *widget)
952 953
{
  GtkNotebook *notebook;
954 955
  GdkWindowAttr attributes;
  gint attributes_mask;
956
  GdkRectangle event_window_pos;
957

958
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
959

960 961
  notebook = GTK_NOTEBOOK (widget);
  GTK_WIDGET_SET_FLAGS (notebook, GTK_REALIZED);
962

963 964 965 966 967
  gtk_notebook_get_event_window_position (notebook, &event_window_pos);
  
  widget->window = gtk_widget_get_parent_window (widget);
  gdk_window_ref (widget->window);
  
968
  attributes.window_type = GDK_WINDOW_CHILD;
969 970 971 972 973
  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;
974 975 976
  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);
977

978
  attributes_mask = GDK_WA_X | GDK_WA_Y;
979

980 981 982
  notebook->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), 
					   &attributes, attributes_mask);
  gdk_window_set_user_data (notebook->event_window, notebook);
983

984
  widget->style = gtk_style_attach (widget->style, widget->window);
Elliot Lee's avatar
Elliot Lee committed
985 986
}

987 988 989 990 991 992 993 994 995
static void
gtk_notebook_unrealize (GtkWidget *widget)
{
  GtkNotebook *notebook;

  g_return_if_fail (GTK_IS_NOTEBOOK (widget));

  notebook = GTK_NOTEBOOK (widget);

996 997 998
  gdk_window_set_user_data (notebook->event_window, NULL);
  gdk_window_destroy (notebook->event_window);
  notebook->event_window = NULL;
999 1000 1001 1002 1003

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

1004
static void
1005 1006
gtk_notebook_size_request (GtkWidget      *widget,
			   GtkRequisition *requisition)
1007
{
1008
  GtkNotebook *notebook = GTK_NOTEBOOK (widget);
1009 1010
  GtkNotebookPage *page;
  GList *children;
1011
  GtkRequisition child_requisition;
1012 1013
  gboolean switch_page = FALSE;
  gint vis_pages;
1014
  gint focus_width;
1015

Owen Taylor's avatar