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

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

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


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


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

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

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

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

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

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

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

  GtkRequisition requisition;
  GtkAllocation allocation;
103

104
  guint mnemonic_activate_signal;
105 106 107 108
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  return notebook_type;
}

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

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

Elliot Lee's avatar
Elliot Lee committed
314 315 316
static void
gtk_notebook_class_init (GtkNotebookClass *class)
{
Tim Janik's avatar
Tim Janik committed
317 318 319 320
  GObjectClass   *gobject_class = G_OBJECT_CLASS (class);
  GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
321 322
  GtkBindingSet *binding_set;
  
Tim Janik's avatar
Tim Janik committed
323
  parent_class = g_type_class_peek_parent (class);
324

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

Elliot Lee's avatar
Elliot Lee committed
329 330 331
  widget_class->map = gtk_notebook_map;
  widget_class->unmap = gtk_notebook_unmap;
  widget_class->realize = gtk_notebook_realize;
332
  widget_class->unrealize = gtk_notebook_unrealize;
Elliot Lee's avatar
Elliot Lee committed
333 334 335 336
  widget_class->size_request = gtk_notebook_size_request;
  widget_class->size_allocate = gtk_notebook_size_allocate;
  widget_class->expose_event = gtk_notebook_expose;
  widget_class->button_press_event = gtk_notebook_button_press;
337 338 339 340 341
  widget_class->button_release_event = gtk_notebook_button_release;
  widget_class->enter_notify_event = gtk_notebook_enter_notify;
  widget_class->leave_notify_event = gtk_notebook_leave_notify;
  widget_class->motion_notify_event = gtk_notebook_motion_notify;
  widget_class->focus_in_event = gtk_notebook_focus_in;
342 343
  widget_class->focus = gtk_notebook_focus;
  
Elliot Lee's avatar
Elliot Lee committed
344 345
  container_class->add = gtk_notebook_add;
  container_class->remove = gtk_notebook_remove;
346
  container_class->forall = gtk_notebook_forall;
Lars Hamann's avatar
Lars Hamann committed
347
  container_class->set_focus_child = gtk_notebook_set_focus_child;
Tim Janik's avatar
Tim Janik committed
348 349
  container_class->get_child_property = gtk_notebook_get_child_property;
  container_class->set_child_property = gtk_notebook_set_child_property;
350
  container_class->child_type = gtk_notebook_child_type;
351

352
  class->switch_page = gtk_notebook_real_switch_page;
353

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

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

  gtk_binding_entry_add_signal (binding_set,
                                GDK_Page_Up, GDK_CONTROL_MASK,
                                "change_current_page", 1,
                                G_TYPE_INT, -1);
  gtk_binding_entry_add_signal (binding_set,
                                GDK_Page_Down, GDK_CONTROL_MASK,
                                "change_current_page", 1,
                                G_TYPE_INT, 1);
564 565 566 567 568 569 570 571

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

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

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

  notebook->cur_page = NULL;
  notebook->children = NULL;
  notebook->first_tab = NULL;
  notebook->focus_tab = NULL;
584
  notebook->event_window = NULL;
585 586 587 588 589 590 591 592 593 594 595 596 597 598
  notebook->menu = NULL;

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

  notebook->show_tabs = TRUE;
  notebook->show_border = TRUE;
  notebook->tab_pos = GTK_POS_TOP;
  notebook->scrollable = FALSE;
  notebook->in_child = 0;
  notebook->click_child = 0;
  notebook->button = 0;
  notebook->need_timer = 0;
  notebook->child_has_focus = FALSE;
599
  notebook->have_visible_child = FALSE;
600
  notebook->focus_out = FALSE;
601 602
}

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

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

  if (gtk_widget_is_focus (GTK_WIDGET (notebook)))
623
    {
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
      switch (type)
	{
	case GTK_NOTEBOOK_TAB_FIRST:
	  list = gtk_notebook_search_page (notebook, NULL, STEP_NEXT, TRUE);
	  if (list)
	    gtk_notebook_switch_focus_tab (notebook, list);
	  break;
	case GTK_NOTEBOOK_TAB_LAST:
	  list = gtk_notebook_search_page (notebook, NULL, STEP_PREV, TRUE);
	  if (list)
	    gtk_notebook_switch_focus_tab (notebook, list);
	  break;
	}

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

644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
static void
gtk_notebook_change_current_page (GtkNotebook *notebook,
				  gint         offset)
{
  GList *current = NULL;
  
  if (notebook->cur_page)
    current = g_list_find (notebook->children, notebook->cur_page);

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

  if (current)
    gtk_notebook_switch_page (notebook, current->data, -1);
661
  else
662
    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
}

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

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

#undef D

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

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

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

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

716 717
}

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

 * Return value: the newly created #GtkNotebook
 **/
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
GtkWidget*
gtk_notebook_new (void)
{
  return GTK_WIDGET (gtk_type_new (gtk_notebook_get_type ()));
}

/* Private GtkObject Methods :
 * 
 * gtk_notebook_destroy
 * gtk_notebook_set_arg
 * gtk_notebook_get_arg
 */
static void
gtk_notebook_destroy (GtkObject *object)
{
740
  GtkNotebook *notebook = GTK_NOTEBOOK (object);
741 742 743 744 745 746 747 748
  
  if (notebook->menu)
    gtk_notebook_popup_disable (notebook);

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

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

  notebook = GTK_NOTEBOOK (object);

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

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

  notebook = GTK_NOTEBOOK (object);

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

843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
/* Private GtkWidget Methods :
 * 
 * gtk_notebook_map
 * gtk_notebook_unmap
 * gtk_notebook_realize
 * gtk_notebook_size_request
 * gtk_notebook_size_allocate
 * gtk_notebook_expose
 * gtk_notebook_button_press
 * gtk_notebook_button_release
 * gtk_notebook_enter_notify
 * gtk_notebook_leave_notify
 * gtk_notebook_motion_notify
 * gtk_notebook_focus_in
 * gtk_notebook_focus_out
 * gtk_notebook_draw_focus
 * gtk_notebook_style_set
 */
861 862 863 864 865
static gboolean
gtk_notebook_get_event_window_position (GtkNotebook  *notebook,
					GdkRectangle *rectangle)
{
  GtkWidget *widget = GTK_WIDGET (notebook);
866
  gint border_width = GTK_CONTAINER (notebook)->border_width;
867 868
  GtkNotebookPage *visible_page = NULL;
  GList *tmp_list;
869

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

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

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

  return FALSE;
}

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

927
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
928

929
  GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
930

931
  notebook = GTK_NOTEBOOK (widget);
932

933 934 935 936
  if (notebook->cur_page && 
      GTK_WIDGET_VISIBLE (notebook->cur_page->child) &&
      !GTK_WIDGET_MAPPED (notebook->cur_page->child))
    gtk_widget_map (notebook->cur_page->child);
937

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

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

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

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

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

965
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
966 967

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

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

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

980
  g_return_if_fail (GTK_IS_NOTEBOOK (widget));
981

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

985 986 987 988 989
  gtk_notebook_get_event_window_position (notebook, &event_window_pos);
  
  widget->window = gtk_widget_get_parent_window (widget);
  gdk_window_ref (widget->window);
  
990
  attributes.window_type = GDK_WINDOW_CHILD;
991 992 993 994 995
  attributes.x = event_window_pos.x;
  attributes.y = event_window_pos.y;
  attributes.width = event_window_pos.width;
  attributes.height = event_window_pos.height;
  attributes.wclass = GDK_INPUT_ONLY;
996 997 998 </