1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 * anjuta-notebook-tabber
4 * Copyright (C) Johannes Schmid 2010 <jhs@gnome.org>
6 * anjuta-notebook-tabber is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * anjuta-notebook-tabber is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "anjuta-tabber.h"
22 struct _AnjutaTabberPriv
24 GtkNotebook
* notebook
;
28 /* Style information (taken from GtkNotebook) */
32 GdkWindow
* event_window
;
41 #define ANJUTA_TABBER_GET_PRIVATE(o) \
42 (G_TYPE_INSTANCE_GET_PRIVATE ((o), ANJUTA_TYPE_TABBER, AnjutaTabberPriv))
45 G_DEFINE_TYPE (AnjutaTabber
, anjuta_tabber
, GTK_TYPE_CONTAINER
);
48 anjuta_tabber_init (AnjutaTabber
*object
)
50 AnjutaTabber
* tabber
= ANJUTA_TABBER (object
);
51 AnjutaTabberPriv
* priv
;
53 tabber
->priv
= ANJUTA_TABBER_GET_PRIVATE (tabber
);
56 priv
->children
= NULL
;
57 priv
->active_page
= 0;
59 priv
->tab_hborder
= 2;
60 priv
->tab_vborder
= 2;
62 gtk_widget_set_has_window (GTK_WIDGET(tabber
), FALSE
);
66 * anjuta_tabber_notebook_page_removed:
68 * Called when a page is removed from the associated notebook. Removes
69 * the tab for this page
72 anjuta_tabber_notebook_page_removed (GtkNotebook
* notebook
,
73 GtkWidget
* notebook_child
,
77 GtkWidget
* child
= g_list_nth_data (tabber
->priv
->children
, page_num
);
78 gtk_container_remove (GTK_CONTAINER (tabber
), child
);
82 anjuta_tabber_notebook_switch_page (GtkNotebook
* notebook
,
87 tabber
->priv
->active_page
= page_num
;
88 gtk_widget_queue_draw (GTK_WIDGET (tabber
));
92 anjuta_tabber_finalize (GObject
*object
)
94 G_OBJECT_CLASS (anjuta_tabber_parent_class
)->finalize (object
);
98 anjuta_tabber_dispose (GObject
*object
)
100 AnjutaTabber
* tabber
= ANJUTA_TABBER (object
);
101 g_signal_handlers_disconnect_by_func (tabber
->priv
->notebook
,
102 anjuta_tabber_notebook_page_removed
,
104 g_signal_handlers_disconnect_by_func (tabber
->priv
->notebook
,
105 anjuta_tabber_notebook_switch_page
,
108 G_OBJECT_CLASS (anjuta_tabber_parent_class
)->dispose (object
);
112 * anjuta_tabber_connect_notebook:
114 * Connect signals to associated notebook
118 anjuta_tabber_connect_notebook (AnjutaTabber
* tabber
)
120 g_signal_connect (tabber
->priv
->notebook
, "page-removed",
121 G_CALLBACK(anjuta_tabber_notebook_page_removed
), tabber
);
122 g_signal_connect (tabber
->priv
->notebook
, "switch-page",
123 G_CALLBACK(anjuta_tabber_notebook_switch_page
), tabber
);
127 anjuta_tabber_set_property (GObject
*object
, guint prop_id
, const GValue
*value
, GParamSpec
*pspec
)
129 g_return_if_fail (ANJUTA_IS_TABBER (object
));
131 AnjutaTabber
* tabber
= ANJUTA_TABBER (object
);
136 tabber
->priv
->notebook
= g_value_get_object (value
);
137 anjuta_tabber_connect_notebook (tabber
);
140 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
146 anjuta_tabber_get_property (GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
148 g_return_if_fail (ANJUTA_IS_TABBER (object
));
153 g_value_set_object (value
, object
);
156 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
162 anjuta_tabber_get_preferred_width (GtkWidget
* widget
,
166 g_return_if_fail (ANJUTA_IS_TABBER (widget
));
168 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
176 GtkStyle
* style
= gtk_widget_get_style (widget
);
178 xthickness
= style
->xthickness
;
183 gtk_widget_style_get (GTK_WIDGET (tabber
->priv
->notebook
),
184 "focus-line-width", &focus_width
,
185 "tab-curvature", &tab_curvature
,
186 "tab-overlap", &tab_overlap
,
189 padding
= xthickness
+ focus_width
+ tabber
->priv
->tab_hborder
;
191 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
194 gint child_preferred
;
195 gint extra_space
= 2 * (tab_curvature
- tab_overlap
);
197 if (child
== g_list_first (tabber
->priv
->children
))
198 extra_space
+= tab_overlap
;
199 if (child
== g_list_last (tabber
->priv
->children
))
200 extra_space
+= tab_overlap
;
202 gtk_widget_get_preferred_width (GTK_WIDGET (child
->data
), &child_min
, &child_preferred
);
205 *minimum
+= child_min
+ 2 * padding
+ extra_space
;
209 *preferred
+= child_preferred
+ 2 * padding
+ extra_space
;
215 anjuta_tabber_get_preferred_height (GtkWidget
* widget
,
219 g_return_if_fail (ANJUTA_IS_TABBER (widget
));
221 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
225 gtk_widget_style_get (GTK_WIDGET (tabber
),
226 "focus-line-width", &focus_width
,
230 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
233 gint child_preferred
;
235 gtk_widget_get_preferred_height (GTK_WIDGET (child
->data
), &child_min
, &child_preferred
);
238 *minimum
= MAX(*minimum
, child_min
+
239 2 * (focus_width
+ tabber
->priv
->tab_vborder
));
243 *preferred
= MAX(*preferred
, child_preferred
+
244 2 * (focus_width
+ tabber
->priv
->tab_vborder
));
251 anjuta_tabber_size_allocate(GtkWidget
* widget
, GtkAllocation
* allocation
)
253 g_return_if_fail (ANJUTA_IS_TABBER (widget
));
254 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
260 gint n_children
= g_list_length (tabber
->priv
->children
);
265 gtk_widget_style_get (GTK_WIDGET (tabber
),
266 "focus-line-width", &focus_width
,
267 "tab-curvature", &tab_curvature
,
268 "tab-overlap", &tab_overlap
,
271 padding
= focus_width
+ tabber
->priv
->tab_hborder
;
272 tab_space
= tab_curvature
- tab_overlap
;
274 gtk_widget_set_allocation (widget
, allocation
);
276 switch (gtk_widget_get_direction (widget
))
278 case GTK_TEXT_DIR_RTL
:
279 x
= allocation
->x
+ allocation
->width
;
281 case GTK_TEXT_DIR_LTR
:
286 if (gtk_widget_get_realized (widget
))
288 gdk_window_move_resize (tabber
->priv
->event_window
,
289 allocation
->x
, allocation
->y
,
290 allocation
->width
, allocation
->height
);
291 if (gtk_widget_get_mapped (widget
))
292 gdk_window_show_unraised (tabber
->priv
->event_window
);
297 gint total_width
= 2 * tab_overlap
;
298 gboolean use_natural
= FALSE
;
300 gint extra_space
= 0;
301 gint real_width
= allocation
->width
;
303 /* Check if we have enough space for all widgets natural size */
304 child_equal
= real_width
/ n_children
-
305 n_children
* 2 * (padding
+ tab_space
) - 2 * tab_overlap
;
310 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
312 GtkWidget
* child_widget
= GTK_WIDGET (child
->data
);
314 gtk_widget_get_preferred_width (child_widget
, NULL
,
317 total_width
+= natural
+ 2 * (padding
+ tab_space
);
318 if (natural
< child_equal
)
319 extra_space
+= child_equal
- natural
;
321 use_natural
= (total_width
<= real_width
);
322 child_equal
+= extra_space
/ n_children
;
324 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
326 GtkWidget
* child_widget
= GTK_WIDGET (child
->data
);
327 GtkAllocation child_alloc
;
330 gint begin_tab
= tab_space
;
331 gint end_tab
= tab_space
;
333 if (child
== g_list_first (tabber
->priv
->children
))
334 begin_tab
+= tab_overlap
;
335 if (child
== g_list_last (tabber
->priv
->children
))
336 end_tab
+= tab_overlap
;
338 gtk_widget_get_preferred_width (child_widget
, &minimal
,
343 child_alloc
.width
= natural
;
347 if (natural
< child_equal
)
348 child_alloc
.width
= natural
;
350 child_alloc
.width
= child_equal
;
352 child_alloc
.height
= allocation
->height
353 - 2 * (focus_width
+ tabber
->priv
->tab_vborder
);
354 switch (gtk_widget_get_direction (widget
))
356 case GTK_TEXT_DIR_RTL
:
357 child_alloc
.x
= x
- padding
- begin_tab
- child_alloc
.width
;
358 x
= child_alloc
.x
- padding
- end_tab
;
360 case GTK_TEXT_DIR_LTR
:
362 child_alloc
.x
= x
+ padding
+ begin_tab
;
363 x
= child_alloc
.x
+ child_alloc
.width
+ padding
+ end_tab
;
365 child_alloc
.y
= allocation
->y
+
366 tabber
->priv
->tab_vborder
+ focus_width
;
368 gtk_widget_size_allocate (GTK_WIDGET (child
->data
), &child_alloc
);
374 anjuta_tabber_render_tab (GtkWidget
* widget
,
378 GtkRegionFlags region_flags
)
380 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
382 GtkAllocation widget_alloc
;
392 GtkStyleContext
* context
= gtk_widget_get_style_context (widget
);
395 gtk_widget_set_state_flags (tab
, GTK_STATE_FLAG_ACTIVE
, TRUE
);
397 gtk_widget_unset_state_flags (tab
, GTK_STATE_FLAG_ACTIVE
);
399 gtk_widget_style_get (widget
,
400 "focus-line-width", &focus_width
,
401 "tab-curvature", &tab_curvature
,
402 "tab-overlap", &tab_overlap
,
405 /* Get border/padding for tab */
406 gtk_style_context_save (context
);
407 gtk_style_context_add_class (context
, GTK_STYLE_CLASS_NOTEBOOK
);
408 gtk_style_context_add_region (context
, GTK_STYLE_REGION_TAB
,
411 gtk_style_context_set_state (context
, GTK_STATE_FLAG_ACTIVE
);
412 if (gtk_widget_get_direction (widget
) == GTK_TEXT_DIR_LTR
)
413 gtk_style_context_set_junction_sides (context
,
414 GTK_JUNCTION_CORNER_TOPLEFT
);
416 gtk_style_context_set_junction_sides (context
,
417 GTK_JUNCTION_CORNER_TOPRIGHT
);
419 gtk_widget_get_allocation (widget
, &widget_alloc
);
420 gtk_widget_get_allocation (tab
, &alloc
);
422 xpadding
= focus_width
+ tabber
->priv
->tab_hborder
;
423 ypadding
= focus_width
+ tabber
->priv
->tab_vborder
;
425 tab_begin
= tab_curvature
- tab_overlap
;
426 tab_end
= tab_curvature
- tab_overlap
;
428 if (region_flags
| GTK_REGION_FIRST
)
429 tab_begin
+= tab_overlap
;
430 if (region_flags
| GTK_REGION_LAST
)
431 tab_end
+= tab_overlap
;
433 alloc
.x
-= widget_alloc
.x
;
434 alloc
.x
-= tab_begin
;
436 alloc
.y
-= widget_alloc
.y
;
438 alloc
.width
+= 2 * (xpadding
) + tab_begin
+ tab_end
;
439 alloc
.height
+= 2 * ypadding
;
441 gtk_render_extension (context
,
449 if (gtk_widget_has_focus (widget
) &&
452 GtkAllocation allocation
;
454 gtk_widget_get_allocation (tab
, &allocation
);
456 gtk_render_focus (context
, cr
,
457 allocation
.x
- focus_width
,
458 allocation
.y
- focus_width
,
459 allocation
.width
+ 2 * focus_width
,
460 allocation
.height
+ 2 * focus_width
);
463 gtk_style_context_restore (context
);
466 static GtkRegionFlags
467 anjuta_tabber_get_region_flags (gint page_num
, gboolean is_last
)
469 GtkRegionFlags flags
= 0;
470 if ((page_num
) % 2 == 0)
471 flags
|= GTK_REGION_EVEN
;
473 flags
|= GTK_REGION_ODD
;
476 flags
|= GTK_REGION_FIRST
;
479 flags
|= GTK_REGION_LAST
;
485 anjuta_tabber_draw_tab (AnjutaTabber
* tabber
,
490 GtkWidget
* tab
= GTK_WIDGET (child
->data
);
491 gint nth
= g_list_index (tabber
->priv
->children
,
493 gboolean last
= (child
->next
== NULL
);
494 anjuta_tabber_render_tab (GTK_WIDGET (tabber
), tab
, cr
, current
,
495 anjuta_tabber_get_region_flags (nth
, last
));
499 anjuta_tabber_draw (GtkWidget
* widget
, cairo_t
* cr
)
501 AnjutaTabber
* tabber
;
505 g_return_val_if_fail (ANJUTA_IS_TABBER (widget
), FALSE
);
507 tabber
= ANJUTA_TABBER (widget
);
509 if (!tabber
->priv
->children
)
512 current_tab
= g_list_nth (tabber
->priv
->children
, tabber
->priv
->active_page
);
514 for (child
= tabber
->priv
->children
; child
!= current_tab
; child
= g_list_next (child
))
516 anjuta_tabber_draw_tab (tabber
, cr
, child
, FALSE
);
518 for (child
= g_list_last (tabber
->priv
->children
); child
!= current_tab
; child
= g_list_previous (child
))
520 anjuta_tabber_draw_tab (tabber
, cr
, child
, FALSE
);
522 anjuta_tabber_draw_tab (tabber
, cr
, current_tab
, TRUE
);
523 return GTK_WIDGET_CLASS (anjuta_tabber_parent_class
)->draw (widget
, cr
);
527 * anjuta_tabber_get_widget_coordintes
528 * @widget: widget for the coordinates
529 * @event: event to get coordinates from
530 * @x: return location for x coordinate
531 * @y: return location for y coordinate
533 * Returns: TRUE if coordinates were set, FALSE otherwise
536 anjuta_tabber_get_widget_coordinates (GtkWidget
*widget
,
541 GdkWindow
*window
= ((GdkEventAny
*)event
)->window
;
544 if (!gdk_event_get_coords (event
, &tx
, &ty
))
547 while (window
&& window
!= gtk_widget_get_window (widget
))
549 gint window_x
, window_y
;
551 gdk_window_get_position (window
, &window_x
, &window_y
);
555 window
= gdk_window_get_parent (window
);
570 anjuta_tabber_button_press_event (GtkWidget
* widget
, GdkEventButton
* event
)
572 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
575 if (event
->button
== 1)
578 if (!anjuta_tabber_get_widget_coordinates (widget
, (GdkEvent
*) event
, &x
, &y
))
581 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
584 gtk_widget_get_allocation (GTK_WIDGET (child
->data
), &alloc
);
586 if (alloc
.x
<= x
&& (alloc
.x
+ alloc
.width
) >= x
&&
587 alloc
.y
<= y
&& (alloc
.y
+ alloc
.height
) >= y
)
589 gint page
= g_list_position (tabber
->priv
->children
, child
);
590 gtk_notebook_set_current_page (tabber
->priv
->notebook
, page
);
600 anjuta_tabber_realize (GtkWidget
*widget
)
603 GdkWindowAttr attributes
;
604 GtkAllocation allocation
;
605 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
607 gtk_widget_set_realized (widget
, TRUE
);
609 window
= gtk_widget_get_parent_window (widget
);
610 gtk_widget_set_window (widget
, window
);
611 g_object_ref (window
);
613 gtk_widget_get_allocation (widget
, &allocation
);
615 attributes
.window_type
= GDK_WINDOW_CHILD
;
616 attributes
.x
= allocation
.x
;
617 attributes
.y
= allocation
.y
;
618 attributes
.width
= allocation
.width
;
619 attributes
.height
= allocation
.height
;
620 attributes
.wclass
= GDK_INPUT_ONLY
;
621 attributes
.event_mask
= gtk_widget_get_events (widget
);
622 attributes
.event_mask
|= (GDK_BUTTON_PRESS_MASK
);
624 tabber
->priv
->event_window
= gdk_window_new (gtk_widget_get_parent_window (widget
),
625 &attributes
, GDK_WA_X
| GDK_WA_Y
);
626 gdk_window_set_user_data (tabber
->priv
->event_window
, tabber
);
628 gtk_widget_style_attach (widget
);
632 anjuta_tabber_unrealize (GtkWidget
*widget
)
634 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
635 gdk_window_set_user_data (tabber
->priv
->event_window
, NULL
);
636 gdk_window_destroy (tabber
->priv
->event_window
);
637 tabber
->priv
->event_window
= NULL
;
639 GTK_WIDGET_CLASS (anjuta_tabber_parent_class
)->unrealize (widget
);
643 anjuta_tabber_map (GtkWidget
* widget
)
645 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
646 gtk_widget_set_mapped (widget
, TRUE
);
648 gdk_window_show_unraised (tabber
->priv
->event_window
);
650 GTK_WIDGET_CLASS (anjuta_tabber_parent_class
)->map (widget
);
654 anjuta_tabber_unmap (GtkWidget
* widget
)
656 AnjutaTabber
* tabber
= ANJUTA_TABBER (widget
);
658 gtk_widget_set_mapped (widget
, FALSE
);
659 gdk_window_hide (tabber
->priv
->event_window
);
661 GTK_WIDGET_CLASS (anjuta_tabber_parent_class
)->unmap (widget
);
666 anjuta_tabber_add (GtkContainer
* container
, GtkWidget
* widget
)
668 g_return_if_fail (ANJUTA_IS_TABBER (container
));
669 g_return_if_fail (GTK_IS_WIDGET (widget
));
671 AnjutaTabber
* tabber
= ANJUTA_TABBER (container
);
672 gboolean visible
= gtk_widget_get_visible (widget
);
674 tabber
->priv
->children
= g_list_append (tabber
->priv
->children
, widget
);
675 gtk_widget_set_parent (widget
, GTK_WIDGET (tabber
));
678 gtk_container_resize_children (GTK_CONTAINER (tabber
));
679 gtk_widget_queue_resize (widget
);
684 anjuta_tabber_remove (GtkContainer
* container
, GtkWidget
* widget
)
686 g_return_if_fail (ANJUTA_IS_TABBER (container
));
687 g_return_if_fail (GTK_IS_WIDGET (widget
));
689 AnjutaTabber
* tabber
= ANJUTA_TABBER (container
);
690 gboolean visible
= gtk_widget_get_visible (widget
);
692 gtk_widget_unparent (widget
);
693 tabber
->priv
->children
= g_list_remove (tabber
->priv
->children
, widget
);
695 if (tabber
->priv
->active_page
> 0)
696 tabber
->priv
->active_page
--;
699 gtk_widget_queue_resize (GTK_WIDGET (tabber
));
703 anjuta_tabber_forall (GtkContainer
* container
,
704 gboolean include_internals
,
705 GtkCallback callback
,
706 gpointer callback_data
)
708 g_return_if_fail (ANJUTA_IS_TABBER (container
));
709 AnjutaTabber
* tabber
= ANJUTA_TABBER (container
);
711 for (child
= tabber
->priv
->children
; child
!= NULL
; child
= g_list_next (child
))
713 (* callback
) (GTK_WIDGET(child
->data
), callback_data
);
718 anjuta_tabber_class_init (AnjutaTabberClass
*klass
)
720 GObjectClass
* object_class
= G_OBJECT_CLASS (klass
);
721 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS (klass
);
722 GtkContainerClass
* container_class
= GTK_CONTAINER_CLASS (klass
);
724 object_class
->finalize
= anjuta_tabber_finalize
;
725 object_class
->dispose
= anjuta_tabber_dispose
;
726 object_class
->set_property
= anjuta_tabber_set_property
;
727 object_class
->get_property
= anjuta_tabber_get_property
;
729 widget_class
->get_preferred_height
= anjuta_tabber_get_preferred_height
;
730 widget_class
->get_preferred_width
= anjuta_tabber_get_preferred_width
;
731 widget_class
->size_allocate
= anjuta_tabber_size_allocate
;
732 widget_class
->draw
= anjuta_tabber_draw
;
733 widget_class
->button_press_event
= anjuta_tabber_button_press_event
;
734 widget_class
->realize
= anjuta_tabber_realize
;
735 widget_class
->unrealize
= anjuta_tabber_unrealize
;
736 widget_class
->map
= anjuta_tabber_map
;
737 widget_class
->unmap
= anjuta_tabber_unmap
;
739 container_class
->add
= anjuta_tabber_add
;
740 container_class
->remove
= anjuta_tabber_remove
;
741 container_class
->forall
= anjuta_tabber_forall
;
743 g_object_class_install_property (object_class
,
745 g_param_spec_object ("notebook",
747 "GtkNotebook the tabber is associated with",
749 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_WRITABLE
));
751 /* Install some notebook properties */
752 gtk_widget_class_install_style_property (widget_class
,
753 g_param_spec_int ("tab-overlap", "", "",
758 gtk_widget_class_install_style_property (widget_class
,
759 g_param_spec_int ("tab-curvature", "", "",
765 g_type_class_add_private (klass
, sizeof (AnjutaTabberPriv
));
770 * @notebook: the GtkNotebook the tabber should be associated with
772 * Creates a new AnjutaTabber widget
774 * Returns: newly created AnjutaTabber widget
776 GtkWidget
* anjuta_tabber_new (GtkNotebook
* notebook
)
779 tabber
= GTK_WIDGET (g_object_new (ANJUTA_TYPE_TABBER
, "notebook", notebook
, NULL
));
785 * anjuta_tabber_add_tab:
786 * @tabber: a AnjutaTabber widget
787 * @tab_label: widget used as tab label
789 * Adds a tab to the AnjutaTabber widget
791 void anjuta_tabber_add_tab (AnjutaTabber
* tabber
, GtkWidget
* tab_label
)
793 gtk_container_add (GTK_CONTAINER (tabber
), tab_label
);