2 * Copyright © 2003 David Bordoley
3 * Copyright © 2003-2004 Christian Persch
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 #include "ephy-tabs-menu.h"
25 #include "ephy-notebook.h"
26 #include "ephy-marshal.h"
27 #include "ephy-shell.h"
28 #include "ephy-debug.h"
30 #include <glib/gi18n.h>
31 #include <gtk/gtklabel.h>
32 #include <gtk/gtkmenuitem.h>
33 #include <gtk/gtkaccelmap.h>
34 #include <gtk/gtkaction.h>
35 #include <gtk/gtktoggleaction.h>
36 #include <gtk/gtkradioaction.h>
37 #include <gtk/gtkuimanager.h>
40 #include <libxml/entities.h>
42 #define LABEL_WIDTH_CHARS 32
43 #define ACTION_VERB_FORMAT "JmpTab%x"
44 #define ACTION_VERB_FORMAT_LENGTH strlen (ACTION_VERB_FORMAT) + 14 + 1
45 #define ACCEL_PATH_FORMAT "<Actions>/TabsActions/%s"
46 #define ACCEL_PATH_FORMAT_LENGTH strlen (ACCEL_PATH_FORMAT) -2 + ACTION_VERB_FORMAT_LENGTH
47 #define DATA_KEY "EphyTabsMenu::Action"
49 #define EPHY_TABS_MENU_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_TABS_MENU, EphyTabsMenuPrivate))
51 struct _EphyTabsMenuPrivate
54 GtkActionGroup *action_group;
55 GtkAction *anchor_action;
65 static void ephy_tabs_menu_class_init (EphyTabsMenuClass *klass);
66 static void ephy_tabs_menu_init (EphyTabsMenu *menu);
69 ephy_tabs_menu_get_type (void)
71 static GType type = 0;
73 if (G_UNLIKELY (type == 0))
75 const GTypeInfo our_info =
77 sizeof (EphyTabsMenuClass),
79 NULL, /* base_finalize */
80 (GClassInitFunc) ephy_tabs_menu_class_init,
82 NULL, /* class_data */
85 (GInstanceInitFunc) ephy_tabs_menu_init
88 type = g_type_register_static (G_TYPE_OBJECT,
97 tab_action_activate_cb (GtkToggleAction *action,
100 EphyTabsMenuPrivate *priv = menu->priv;
103 if (gtk_toggle_action_get_active (action) == FALSE)
108 tab = g_object_get_data (G_OBJECT (action), DATA_KEY);
109 g_return_if_fail (tab != NULL);
111 LOG ("tab_action_activate_cb tab=%p", tab);
113 if (ephy_window_get_active_tab (priv->window) != tab)
115 ephy_window_jump_to_tab (priv->window, tab);
120 sync_tab_title (EphyTab *tab,
126 title = ephy_tab_get_title_composite (tab);
128 g_object_set (action, "label", title, NULL);
132 notebook_page_added_cb (EphyNotebook *notebook,
137 EphyTabsMenuPrivate *priv = menu->priv;
139 char verb[ACTION_VERB_FORMAT_LENGTH];
142 LOG ("tab_added_cb tab=%p", tab);
144 g_snprintf (verb, sizeof (verb), ACTION_VERB_FORMAT,
145 _ephy_tab_get_id (tab));
147 action = g_object_new (GTK_TYPE_RADIO_ACTION,
149 "tooltip", _("Switch to this tab"),
152 sync_tab_title (tab, NULL, action);
153 /* make sure the action is alive when handling the signal, see bug #169833 */
154 g_signal_connect_object (tab, "notify::title",
155 G_CALLBACK (sync_tab_title), action, 0);
157 gtk_action_group_add_action_with_accel (priv->action_group, action, NULL);
159 group = gtk_radio_action_get_group (GTK_RADIO_ACTION (priv->anchor_action));
160 gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
162 /* set this here too, since tab-added comes after notify::active-tab */
163 if (ephy_window_get_active_tab (priv->window) == tab)
165 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
168 g_object_set_data (G_OBJECT (tab), DATA_KEY, action);
169 g_object_set_data (G_OBJECT (action), DATA_KEY, tab);
171 g_signal_connect (action, "activate",
172 G_CALLBACK (tab_action_activate_cb), menu);
174 g_object_unref (action);
176 ephy_tabs_menu_update (menu);
180 notebook_page_removed_cb (EphyNotebook *notebook,
185 EphyTabsMenuPrivate *priv = menu->priv;
188 LOG ("tab_removed_cb tab=%p", tab);
190 action = g_object_get_data (G_OBJECT (tab), DATA_KEY);
191 g_return_if_fail (action != NULL);
193 g_signal_handlers_disconnect_by_func
194 (tab, G_CALLBACK (sync_tab_title), action);
196 g_signal_handlers_disconnect_by_func
197 (action, G_CALLBACK (tab_action_activate_cb), menu);
199 g_object_set_data (G_OBJECT (tab), DATA_KEY, NULL);
200 gtk_action_group_remove_action (priv->action_group, action);
202 ephy_tabs_menu_update (menu);
206 notebook_page_reordered_cb (EphyNotebook *notebook,
211 LOG ("tabs_reordered_cb");
213 ephy_tabs_menu_update (menu);
217 connect_proxy_cb (GtkActionGroup *action_group,
222 if (GTK_IS_MENU_ITEM (proxy))
226 label = GTK_LABEL (GTK_BIN (proxy)->child);
228 gtk_label_set_use_underline (label, FALSE);
229 gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END);
230 gtk_label_set_max_width_chars (label, LABEL_WIDTH_CHARS);
235 sync_active_tab (EphyWindow *window,
242 tab = ephy_window_get_active_tab (window);
243 if (tab == NULL) return;
245 LOG ("active tab is tab %p", tab);
247 action = g_object_get_data (G_OBJECT (tab), DATA_KEY);
248 /* happens initially, since the ::active-tab comes before
249 * the ::tab-added signal
251 /* FIXME that's not true with gtk+ 2.9 anymore */
252 if (action == NULL) return;
254 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
258 ephy_tabs_menu_set_window (EphyTabsMenu *menu,
261 EphyTabsMenuPrivate *priv = menu->priv;
263 GtkUIManager *manager;
265 priv->window = window;
267 manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (window));
268 priv->action_group = gtk_action_group_new ("TabsActions");
269 gtk_ui_manager_insert_action_group (manager, priv->action_group, -1);
270 g_object_unref (priv->action_group);
272 priv->anchor_action = g_object_new (GTK_TYPE_RADIO_ACTION,
273 "name", "TabsMenuAnchorAction",
275 gtk_action_group_add_action (priv->action_group, priv->anchor_action);
277 g_signal_connect (priv->action_group, "connect-proxy",
278 G_CALLBACK (connect_proxy_cb), NULL);
280 g_signal_connect (window, "notify::active-tab",
281 G_CALLBACK (sync_active_tab), menu);
283 notebook = ephy_window_get_notebook (window);
284 g_signal_connect_object (notebook, "page-added",
285 G_CALLBACK (notebook_page_added_cb), menu, 0);
286 g_signal_connect_object (notebook, "page-removed",
287 G_CALLBACK (notebook_page_removed_cb), menu, 0);
288 g_signal_connect_object (notebook, "page-reordered",
289 G_CALLBACK (notebook_page_reordered_cb), menu, 0);
293 ephy_tabs_menu_set_property (GObject *object,
298 EphyTabsMenu *menu = EPHY_TABS_MENU (object);
303 ephy_tabs_menu_set_window (menu, g_value_get_object (value));
309 ephy_tabs_menu_get_property (GObject *object,
314 /* no readable properties */
315 g_return_if_reached ();
319 ephy_tabs_menu_class_init (EphyTabsMenuClass *klass)
321 GObjectClass *object_class = G_OBJECT_CLASS (klass);
323 object_class->set_property = ephy_tabs_menu_set_property;
324 object_class->get_property = ephy_tabs_menu_get_property;
326 g_object_class_install_property (object_class,
328 g_param_spec_object ("window",
332 G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
333 G_PARAM_CONSTRUCT_ONLY));
335 g_type_class_add_private (object_class, sizeof (EphyTabsMenuPrivate));
339 ephy_tabs_menu_init (EphyTabsMenu *menu)
341 menu->priv = EPHY_TABS_MENU_GET_PRIVATE (menu);
345 ephy_tabs_menu_clean (EphyTabsMenu *menu)
347 EphyTabsMenuPrivate *p = menu->priv;
348 GtkUIManager *manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (p->window));
352 gtk_ui_manager_remove_ui (manager, p->ui_id);
353 gtk_ui_manager_ensure_update (manager);
359 ephy_tabs_menu_new (EphyWindow *window)
361 return EPHY_TABS_MENU (g_object_new (EPHY_TYPE_TABS_MENU,
367 tab_set_action_accelerator (GtkActionGroup *action_group,
372 char accel_path[ACCEL_PATH_FORMAT_LENGTH];
376 GdkModifierType accel_mods;
378 verb = gtk_action_get_name (action);
380 /* set the accel path for the menu item */
381 g_snprintf (accel_path, sizeof (accel_path),
382 ACCEL_PATH_FORMAT, verb);
383 gtk_action_set_accel_path (action, accel_path);
385 /* Only the first ten tabs get accelerators starting from 1 through 0 */
389 accel_number = (tab_number + 1) % 10;
391 g_snprintf (accel, sizeof (accel), "<alt>%d", accel_number);
393 gtk_accelerator_parse (accel, &accel_key, &accel_mods);
397 gtk_accel_map_change_entry (accel_path, accel_key,
403 gtk_accel_map_change_entry (accel_path, 0, 0, TRUE);
408 ephy_tabs_menu_update (EphyTabsMenu *menu)
410 EphyTabsMenuPrivate *p = menu->priv;
411 GtkUIManager *manager;
413 GList *tabs = NULL, *l;
417 LOG ("Rebuilding open tabs menu");
419 START_PROFILER ("Rebuilding tabs menu")
421 ephy_tabs_menu_clean (menu);
423 tabs = ephy_window_get_tabs (p->window);
425 if (g_list_length (tabs) == 0) return;
427 manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (p->window));
428 p->ui_id = gtk_ui_manager_new_merge_id (manager);
430 for (l = tabs; l != NULL; l = l->next)
432 action = g_object_get_data (G_OBJECT (l->data), DATA_KEY);
433 g_return_if_fail (action != NULL);
435 verb = gtk_action_get_name (action);
437 tab_set_action_accelerator (p->action_group, action, i++);
439 gtk_ui_manager_add_ui (manager, p->ui_id,
440 "/menubar/TabsMenu/TabsOpen",
442 GTK_UI_MANAGER_MENUITEM, FALSE);
447 STOP_PROFILER ("Rebuilding tabs menu")