midori/extensions/shortcuts.c
Christian Dywan d0215c4032 Support keyboard hotkeys that GTK+ considers invalid
We re-implement the usual key handling by overriding the key press
handler, emulating the according steps but leaving out the
validation that normally rejects certain combinations.

Single key hotkeys work unless an entry is focussed, in which case
the entry receives the input, and Control+Tab works as well.

The shortcuts extension is adjusted to accept any hotkeys.
2009-11-04 14:31:16 +01:00

315 lines
11 KiB
C

/*
Copyright (C) 2009 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#include <midori/midori.h>
#include <midori/sokoke.h>
#include "config.h"
static void
shortcuts_browser_populate_tool_menu_cb (MidoriBrowser* browser,
GtkWidget* menu,
MidoriExtension* extension);
static void
shortcuts_app_add_browser_cb (MidoriApp* app,
MidoriBrowser* browser,
MidoriExtension* extension);
static void
shortcuts_deactivate_cb (MidoriExtension* extension,
MidoriBrowser* browser)
{
MidoriApp* app = midori_extension_get_app (extension);
g_signal_handlers_disconnect_by_func (
browser, shortcuts_browser_populate_tool_menu_cb, extension);
g_signal_handlers_disconnect_by_func (
extension, shortcuts_deactivate_cb, browser);
g_signal_handlers_disconnect_by_func (
app, shortcuts_app_add_browser_cb, extension);
}
static void
shortcuts_preferences_render_text (GtkTreeViewColumn* column,
GtkCellRenderer* renderer,
GtkTreeModel* model,
GtkTreeIter* iter,
MidoriExtension* extension)
{
GtkAction* action;
gchar* label;
gchar* stripped;
gtk_tree_model_get (model, iter, 0, &action, -1);
if ((label = katze_object_get_string (action, "label")))
stripped = katze_strip_mnemonics (label);
else
{
GtkStockItem item;
g_object_get (action, "stock-id", &label, NULL);
if (gtk_stock_lookup (label, &item))
stripped = katze_strip_mnemonics (item.label);
else
stripped = g_strdup ("");
}
g_free (label);
g_object_set (renderer, "text", stripped, NULL);
g_free (stripped);
g_object_unref (action);
}
static void
shortcuts_preferences_render_accel (GtkTreeViewColumn* column,
GtkCellRenderer* renderer,
GtkTreeModel* model,
GtkTreeIter* iter,
MidoriExtension* extension)
{
GtkAction* action;
const gchar* accel_path;
GtkAccelKey key;
gtk_tree_model_get (model, iter, 0, &action, -1);
accel_path = gtk_action_get_accel_path (action);
if (accel_path)
{
if (gtk_accel_map_lookup_entry (accel_path, &key))
{
if (key.accel_key)
g_object_set (renderer,
"accel-key", key.accel_key,
"accel-mods", key.accel_mods,
"accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
NULL);
else
g_object_set (renderer, "text", _("None"), NULL);
}
g_object_set (renderer, "sensitive", TRUE, "editable", TRUE, NULL);
}
else
g_object_set (renderer, "text", "", "sensitive", FALSE, NULL);
g_object_unref (action);
}
static void
shortcuts_accel_edited_cb (GtkCellRenderer* renderer,
const gchar* tree_path,
guint accel_key,
GdkModifierType accel_mods,
guint keycode,
GtkTreeModel* model)
{
GtkTreeIter iter;
if (gtk_tree_model_get_iter_from_string (model, &iter, tree_path))
{
GtkAction* action;
const gchar* accel_path;
gtk_tree_model_get (model, &iter, 0, &action, -1);
accel_path = gtk_action_get_accel_path (action);
gtk_accel_map_change_entry (accel_path, accel_key, accel_mods, TRUE);
g_object_unref (action);
}
}
static void
shortcuts_accel_cleared_cb (GtkCellRenderer* renderer,
const gchar* tree_path,
GtkTreeModel* model)
{
GtkTreeIter iter;
if (gtk_tree_model_get_iter_from_string (model, &iter, tree_path))
{
GtkAction* action;
const gchar* accel_path;
gtk_tree_model_get (model, &iter, 0, &action, -1);
accel_path = gtk_action_get_accel_path (action);
gtk_accel_map_change_entry (accel_path, 0, 0, FALSE);
g_object_unref (action);
}
}
static GtkWidget*
shortcuts_get_preferences_dialog (MidoriExtension* extension)
{
MidoriApp* app;
GtkWidget* browser;
const gchar* dialog_title;
GtkWidget* dialog;
gint width, height;
GtkWidget* xfce_heading;
GtkWidget* hbox;
GtkListStore* liststore;
GtkWidget* treeview;
GtkTreeViewColumn* column;
GtkCellRenderer* renderer_text;
GtkCellRenderer* renderer_accel;
GtkWidget* scrolled;
GtkActionGroup* action_group;
GList* actions;
guint i;
GtkAction* action;
#if HAVE_OSX
GtkWidget* icon;
#endif
app = midori_extension_get_app (extension);
browser = katze_object_get_object (app, "browser");
dialog_title = _("Customize Keyboard shortcuts");
dialog = gtk_dialog_new_with_buttons (dialog_title, GTK_WINDOW (browser),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
#if !HAVE_OSX
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
#endif
NULL);
g_signal_connect (dialog, "destroy",
G_CALLBACK (gtk_widget_destroyed), &dialog);
gtk_window_set_icon_name (GTK_WINDOW (dialog), GTK_STOCK_PROPERTIES);
sokoke_widget_get_text_size (dialog, "M", &width, &height);
gtk_window_set_default_size (GTK_WINDOW (dialog), width * 52, height * 24);
g_signal_connect (dialog, "response",
G_CALLBACK (gtk_widget_destroy), dialog);
if ((xfce_heading = sokoke_xfce_header_new (
gtk_window_get_icon_name (GTK_WINDOW (dialog)), dialog_title)))
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
xfce_heading, FALSE, FALSE, 0);
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
TRUE, TRUE, 12);
liststore = gtk_list_store_new (1, GTK_TYPE_ACTION);
treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (liststore));
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
column = gtk_tree_view_column_new ();
renderer_text = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, renderer_text, FALSE);
gtk_tree_view_column_set_cell_data_func (column, renderer_text,
(GtkTreeCellDataFunc)shortcuts_preferences_render_text,
extension, NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
column = gtk_tree_view_column_new ();
renderer_accel = gtk_cell_renderer_accel_new ();
gtk_tree_view_column_pack_start (column, renderer_accel, TRUE);
gtk_tree_view_column_set_cell_data_func (column, renderer_accel,
(GtkTreeCellDataFunc)shortcuts_preferences_render_accel,
extension, NULL);
g_signal_connect (renderer_accel, "accel-edited",
G_CALLBACK (shortcuts_accel_edited_cb), liststore);
g_signal_connect (renderer_accel, "accel-cleared",
G_CALLBACK (shortcuts_accel_cleared_cb), liststore);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add (GTK_CONTAINER (scrolled), treeview);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (hbox), scrolled, TRUE, TRUE, 5);
action_group = midori_browser_get_action_group (MIDORI_BROWSER (browser));
actions = gtk_action_group_list_actions (action_group);
i = 0;
/* FIXME: Catch added and removed actions */
while ((action = g_list_nth_data (actions, i++)))
gtk_list_store_insert_with_values (GTK_LIST_STORE (liststore),
NULL, G_MAXINT, 0, action, -1);
g_list_free (actions);
g_object_unref (liststore);
gtk_widget_show_all (GTK_DIALOG (dialog)->vbox);
g_object_unref (browser);
return dialog;
}
static void
shortcuts_menu_configure_shortcuts_activate_cb (GtkWidget* menuitem,
MidoriExtension* extension)
{
static GtkWidget* dialog = NULL;
if (!dialog)
{
dialog = shortcuts_get_preferences_dialog (extension);
g_signal_connect (dialog, "destroy",
G_CALLBACK (gtk_widget_destroyed), &dialog);
gtk_widget_show (dialog);
}
else
gtk_window_present (GTK_WINDOW (dialog));
}
static void
shortcuts_browser_populate_tool_menu_cb (MidoriBrowser* browser,
GtkWidget* menu,
MidoriExtension* extension)
{
GtkWidget* menuitem;
menuitem = gtk_menu_item_new_with_mnemonic (_("Customize Sh_ortcuts..."));
g_signal_connect (menuitem, "activate",
G_CALLBACK (shortcuts_menu_configure_shortcuts_activate_cb), extension);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
}
static void
shortcuts_app_add_browser_cb (MidoriApp* app,
MidoriBrowser* browser,
MidoriExtension* extension)
{
g_signal_connect (browser, "populate-tool-menu",
G_CALLBACK (shortcuts_browser_populate_tool_menu_cb), extension);
g_signal_connect (extension, "deactivate",
G_CALLBACK (shortcuts_deactivate_cb), browser);
}
static void
shortcuts_activate_cb (MidoriExtension* extension,
MidoriApp* app)
{
KatzeArray* browsers;
MidoriBrowser* browser;
guint i;
browsers = katze_object_get_object (app, "browsers");
i = 0;
while ((browser = katze_array_get_nth_item (browsers, i++)))
shortcuts_app_add_browser_cb (app, browser, extension);
g_signal_connect (app, "add-browser",
G_CALLBACK (shortcuts_app_add_browser_cb), extension);
g_object_unref (browsers);
}
MidoriExtension*
extension_init (void)
{
MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION,
"name", _("Shortcuts"),
"description", _("View and edit keyboard shortcuts"),
"version", "0.1",
"authors", "Christian Dywan <christian@twotoasts.de>",
NULL);
g_signal_connect (extension, "activate",
G_CALLBACK (shortcuts_activate_cb), NULL);
return extension;
}