Commit bb884cb6 authored by Carlos Soriano's avatar Carlos Soriano Committed by Carlos Soriano Sánchez

general: rework menus of nautilus

Refresh the nautilus menus in the view (context menus) and the menus in
the toolbar, changin as well in the path the UI of the toolbar to match
mockups.

In the way, port to GAction and GMenu every action and menu of nautilus
and some clean ups thanks to this port.
parent c17b70df
......@@ -33,428 +33,6 @@
#include <gtk/gtk.h>
#include <string.h>
typedef struct _TargetCallbackData TargetCallbackData;
typedef void (* SelectAllCallback) (gpointer target);
typedef void (* ConnectCallbacksFunc) (GObject *object,
TargetCallbackData *target_data);
static void selection_changed_callback (GtkWidget *widget,
gpointer callback_data);
static void owner_change_callback (GtkClipboard *clipboard,
GdkEventOwnerChange *event,
gpointer callback_data);
struct _TargetCallbackData {
GtkUIManager *ui_manager;
GtkActionGroup *action_group;
gboolean shares_selection_changes;
SelectAllCallback select_all_callback;
ConnectCallbacksFunc connect_callbacks;
ConnectCallbacksFunc disconnect_callbacks;
};
static void
cut_callback (gpointer target)
{
g_assert (target != NULL);
g_signal_emit_by_name (target, "cut-clipboard");
}
static void
copy_callback (gpointer target)
{
g_assert (target != NULL);
g_signal_emit_by_name (target, "copy-clipboard");
}
static void
paste_callback (gpointer target)
{
g_assert (target != NULL);
g_signal_emit_by_name (target, "paste-clipboard");
}
static void
editable_select_all_callback (gpointer target)
{
GtkEditable *editable;
editable = GTK_EDITABLE (target);
g_assert (editable != NULL);
gtk_editable_set_position (editable, -1);
gtk_editable_select_region (editable, 0, -1);
}
static void
action_cut_callback (GtkAction *action,
gpointer callback_data)
{
cut_callback (callback_data);
}
static void
action_copy_callback (GtkAction *action,
gpointer callback_data)
{
copy_callback (callback_data);
}
static void
action_paste_callback (GtkAction *action,
gpointer callback_data)
{
paste_callback (callback_data);
}
static void
action_select_all_callback (GtkAction *action,
gpointer callback_data)
{
TargetCallbackData *target_data;
g_assert (callback_data != NULL);
target_data = g_object_get_data (callback_data, "Nautilus:clipboard_target_data");
g_assert (target_data != NULL);
target_data->select_all_callback (callback_data);
}
static void
received_clipboard_contents (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
gpointer data)
{
GtkActionGroup *action_group;
GtkAction *action;
action_group = data;
action = gtk_action_group_get_action (action_group,
"Paste");
if (action != NULL) {
gtk_action_set_sensitive (action,
gtk_selection_data_targets_include_text (selection_data));
}
g_object_unref (action_group);
}
static void
set_paste_sensitive_if_clipboard_contains_data (GtkActionGroup *action_group)
{
GtkAction *action;
if (gdk_display_supports_selection_notification (gdk_display_get_default ())) {
gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
gdk_atom_intern ("TARGETS", FALSE),
received_clipboard_contents,
g_object_ref (action_group));
} else {
/* If selection notification isn't supported, always activate Paste */
action = gtk_action_group_get_action (action_group,
"Paste");
gtk_action_set_sensitive (action, TRUE);
}
}
static void
set_clipboard_menu_items_sensitive (GtkActionGroup *action_group)
{
GtkAction *action;
action = gtk_action_group_get_action (action_group,
"Cut");
gtk_action_set_sensitive (action, TRUE);
action = gtk_action_group_get_action (action_group,
"Copy");
gtk_action_set_sensitive (action, TRUE);
}
static void
set_clipboard_menu_items_insensitive (GtkActionGroup *action_group)
{
GtkAction *action;
action = gtk_action_group_get_action (action_group,
"Cut");
gtk_action_set_sensitive (action, FALSE);
action = gtk_action_group_get_action (action_group,
"Copy");
gtk_action_set_sensitive (action, FALSE);
}
static gboolean
clipboard_items_are_merged_in (GtkWidget *widget)
{
return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
"Nautilus:clipboard_menu_items_merged"));
}
static void
set_clipboard_items_are_merged_in (GObject *widget_as_object,
gboolean merged_in)
{
g_object_set_data (widget_as_object,
"Nautilus:clipboard_menu_items_merged",
GINT_TO_POINTER (merged_in));
}
static void
editable_connect_callbacks (GObject *object,
TargetCallbackData *target_data)
{
g_signal_connect_after (object, "selection-changed",
G_CALLBACK (selection_changed_callback), target_data);
selection_changed_callback (GTK_WIDGET (object),
target_data);
}
static void
editable_disconnect_callbacks (GObject *object,
TargetCallbackData *target_data)
{
g_signal_handlers_disconnect_matched (object,
G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
0, 0, NULL,
G_CALLBACK (selection_changed_callback),
target_data);
}
static void
merge_in_clipboard_menu_items (GObject *widget_as_object,
TargetCallbackData *target_data)
{
gboolean add_selection_callback;
g_assert (target_data != NULL);
add_selection_callback = target_data->shares_selection_changes;
gtk_ui_manager_insert_action_group (target_data->ui_manager,
target_data->action_group, 0);
set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
g_signal_connect (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), "owner-change",
G_CALLBACK (owner_change_callback), target_data);
if (add_selection_callback) {
target_data->connect_callbacks (widget_as_object, target_data);
} else {
/* If we don't use sensitivity, everything should be on */
set_clipboard_menu_items_sensitive (target_data->action_group);
}
set_clipboard_items_are_merged_in (widget_as_object, TRUE);
}
static void
merge_out_clipboard_menu_items (GObject *widget_as_object,
TargetCallbackData *target_data)
{
gboolean selection_callback_was_added;
g_assert (target_data != NULL);
gtk_ui_manager_remove_action_group (target_data->ui_manager,
target_data->action_group);
g_signal_handlers_disconnect_matched (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
0, 0, NULL,
G_CALLBACK (owner_change_callback),
target_data);
selection_callback_was_added = target_data->shares_selection_changes;
if (selection_callback_was_added) {
target_data->disconnect_callbacks (widget_as_object, target_data);
}
set_clipboard_items_are_merged_in (widget_as_object, FALSE);
}
static gboolean
focus_changed_callback (GtkWidget *widget,
GdkEventAny *event,
gpointer callback_data)
{
/* Connect the component to the container if the widget has focus. */
if (gtk_widget_has_focus (widget)) {
if (!clipboard_items_are_merged_in (widget)) {
merge_in_clipboard_menu_items (G_OBJECT (widget), callback_data);
}
} else {
if (clipboard_items_are_merged_in (widget)) {
merge_out_clipboard_menu_items (G_OBJECT (widget), callback_data);
}
}
return FALSE;
}
static void
selection_changed_callback (GtkWidget *widget,
gpointer callback_data)
{
TargetCallbackData *target_data;
GtkEditable *editable;
int start, end;
target_data = (TargetCallbackData *) callback_data;
g_assert (target_data != NULL);
editable = GTK_EDITABLE (widget);
g_assert (editable != NULL);
if (gtk_editable_get_selection_bounds (editable, &start, &end) && start != end) {
set_clipboard_menu_items_sensitive (target_data->action_group);
} else {
set_clipboard_menu_items_insensitive (target_data->action_group);
}
}
static void
owner_change_callback (GtkClipboard *clipboard,
GdkEventOwnerChange *event,
gpointer callback_data)
{
TargetCallbackData *target_data;
g_assert (callback_data != NULL);
target_data = callback_data;
set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
}
static void
target_destroy_callback (GtkWidget *object,
gpointer callback_data)
{
g_assert (callback_data != NULL);
if (clipboard_items_are_merged_in (object)) {
merge_out_clipboard_menu_items (G_OBJECT (object), callback_data);
}
}
static void
target_data_free (TargetCallbackData *target_data)
{
g_object_unref (target_data->action_group);
g_free (target_data);
}
static const GtkActionEntry clipboard_entries[] = {
/* name, stock id */ { "Cut", NULL,
/* label, accelerator */ N_("Cu_t"), "<control>X",
/* tooltip */ N_("Cut the selected text to the clipboard"),
G_CALLBACK (action_cut_callback) },
/* name, stock id */ { "Copy", NULL,
/* label, accelerator */ N_("_Copy"), "<control>C",
/* tooltip */ N_("Copy the selected text to the clipboard"),
G_CALLBACK (action_copy_callback) },
/* name, stock id */ { "Paste", NULL,
/* label, accelerator */ N_("_Paste"), "<control>V",
/* tooltip */ N_("Paste the text stored on the clipboard"),
G_CALLBACK (action_paste_callback) },
/* name, stock id */ { "Select All", NULL,
/* label, accelerator */ N_("Select _All"), "<control>A",
/* tooltip */ N_("Select all the text in a text field"),
G_CALLBACK (action_select_all_callback) },
};
static TargetCallbackData *
initialize_clipboard_component_with_callback_data (GtkEditable *target,
GtkUIManager *ui_manager,
gboolean shares_selection_changes,
SelectAllCallback select_all_callback,
ConnectCallbacksFunc connect_callbacks,
ConnectCallbacksFunc disconnect_callbacks)
{
GtkActionGroup *action_group;
TargetCallbackData *target_data;
action_group = gtk_action_group_new ("ClipboardActions");
gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
gtk_action_group_add_actions (action_group,
clipboard_entries, G_N_ELEMENTS (clipboard_entries),
target);
/* Do the actual connection of the UI to the container at
* focus time, and disconnect at both focus and destroy
* time.
*/
target_data = g_new (TargetCallbackData, 1);
target_data->ui_manager = ui_manager;
target_data->action_group = action_group;
target_data->shares_selection_changes = shares_selection_changes;
target_data->select_all_callback = select_all_callback;
target_data->connect_callbacks = connect_callbacks;
target_data->disconnect_callbacks = disconnect_callbacks;
return target_data;
}
static void
nautilus_clipboard_real_set_up (gpointer target,
GtkUIManager *ui_manager,
gboolean shares_selection_changes,
SelectAllCallback select_all_callback,
ConnectCallbacksFunc connect_callbacks,
ConnectCallbacksFunc disconnect_callbacks)
{
TargetCallbackData *target_data;
if (g_object_get_data (G_OBJECT (target), "Nautilus:clipboard_target_data") != NULL) {
return;
}
target_data = initialize_clipboard_component_with_callback_data
(target,
ui_manager,
shares_selection_changes,
select_all_callback,
connect_callbacks,
disconnect_callbacks);
g_signal_connect (target, "focus-in-event",
G_CALLBACK (focus_changed_callback), target_data);
g_signal_connect (target, "focus-out-event",
G_CALLBACK (focus_changed_callback), target_data);
g_signal_connect (target, "destroy",
G_CALLBACK (target_destroy_callback), target_data);
g_object_set_data_full (G_OBJECT (target), "Nautilus:clipboard_target_data",
target_data, (GDestroyNotify) target_data_free);
/* Call the focus changed callback once to merge if the window is
* already in focus.
*/
focus_changed_callback (GTK_WIDGET (target), NULL, target_data);
}
void
nautilus_clipboard_set_up_editable (GtkEditable *target,
GtkUIManager *ui_manager,
gboolean shares_selection_changes)
{
g_return_if_fail (GTK_IS_EDITABLE (target));
g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
nautilus_clipboard_real_set_up (target, ui_manager,
shares_selection_changes,
editable_select_all_callback,
editable_connect_callbacks,
editable_disconnect_callbacks);
}
static GList *
convert_lines_to_str_list (char **lines, gboolean *cut)
{
......
......@@ -26,18 +26,6 @@
#include <gtk/gtk.h>
/* This makes this editable or text view put clipboard commands into
* the passed UI manager when the editable/text view is in focus.
* Callers in Nautilus normally get the UI manager from
* nautilus_window_get_ui_manager. */
/* The shares selection changes argument should be set to true if the
* widget uses the signal "selection-changed" to tell others about
* text selection changes. The NautilusEntry widget
* is currently the only editable in nautilus that shares selection
* changes. */
void nautilus_clipboard_set_up_editable (GtkEditable *target,
GtkUIManager *ui_manager,
gboolean shares_selection_changes);
void nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget,
const GList *item_uris,
GdkAtom copied_files_atom);
......
......@@ -29,87 +29,146 @@
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <string.h>
void
nautilus_ui_unmerge_ui (GtkUIManager *ui_manager,
guint *merge_id,
GtkActionGroup **action_group)
static GMenuModel *
find_gmenu_model (GMenuModel *model,
const gchar *model_id)
{
if (*merge_id != 0) {
gtk_ui_manager_remove_ui (ui_manager,
*merge_id);
*merge_id = 0;
}
if (*action_group != NULL) {
gtk_ui_manager_remove_action_group (ui_manager,
*action_group);
*action_group = NULL;
gint i, n_items;
GMenuModel *insertion_model = NULL;
n_items = g_menu_model_get_n_items (model);
for (i = 0; i < n_items && !insertion_model; i++) {
gchar *id = NULL;
if (g_menu_model_get_item_attribute (model, i, "id", "s", &id) &&
g_strcmp0 (id, model_id) == 0) {
insertion_model = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
if (!insertion_model)
insertion_model = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
} else {
GMenuModel *submodel;
GMenuModel *submenu;
gint j, j_items;
submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION);
if (!submodel) {
submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU);
if (!submodel)
continue;
}
j_items = g_menu_model_get_n_items (submodel);
for (j = 0; j < j_items && !insertion_model; j++) {
submenu = g_menu_model_get_item_link (submodel, j, G_MENU_LINK_SUBMENU);
if (submenu)
insertion_model = find_gmenu_model (submenu, model_id);
}
}
g_free (id);
}
return insertion_model;
}
/*
* The original GMenu is modified adding to the section @submodel_name
* the items in @gmenu_to_merge.
* @gmenu_to_merge should be a list of menu items.
*/
void
nautilus_ui_prepare_merge_ui (GtkUIManager *ui_manager,
const char *name,
guint *merge_id,
GtkActionGroup **action_group)
nautilus_gmenu_merge (GMenu *original,
GMenu *gmenu_to_merge,
const gchar *submodel_name,
gboolean prepend)
{
*merge_id = gtk_ui_manager_new_merge_id (ui_manager);
*action_group = gtk_action_group_new (name);
gtk_action_group_set_translation_domain (*action_group, GETTEXT_PACKAGE);
gtk_ui_manager_insert_action_group (ui_manager, *action_group, 0);
g_object_unref (*action_group); /* owned by ui manager */
}
gint i, n_items;
GMenuModel *submodel;
GMenuItem *item;
static void
extension_action_callback (GtkAction *action,
gpointer callback_data)
{
nautilus_menu_item_activate (NAUTILUS_MENU_ITEM (callback_data));
g_return_if_fail (G_IS_MENU (original));
g_return_if_fail (G_IS_MENU (gmenu_to_merge));
submodel = find_gmenu_model (G_MENU_MODEL (original), submodel_name);
g_return_if_fail (submodel != NULL);
n_items = g_menu_model_get_n_items (G_MENU_MODEL (gmenu_to_merge));
for (i = 0; i < n_items; i++) {
item = g_menu_item_new_from_model (G_MENU_MODEL (gmenu_to_merge), i);
if (prepend)
g_menu_prepend_item (G_MENU (submodel), item);
else
g_menu_append_item (G_MENU (submodel), item);
g_object_unref (item);
}
}
GtkAction *
nautilus_action_from_menu_item (NautilusMenuItem *item,
GtkWidget *parent_widget)
/*
* The GMenu @menu is modified adding to the section @submodel_name
* the item @item.
*/
void
nautilus_gmenu_add_item_in_submodel (GMenu *menu,
GMenuItem *item,
const gchar *submodel_name,
gboolean prepend)
{
char *name, *label, *tip, *icon_name;
gboolean sensitive, priority;
GtkAction *action;
GdkPixbuf *pixbuf;
GMenuModel *submodel;
g_object_get (G_OBJECT (item),
"name", &name, "label", &label,
"tip", &tip, "icon", &icon_name,
"sensitive", &sensitive,
"priority", &priority,
NULL);
action = gtk_action_new (name,
label,
tip,
NULL);
if (icon_name != NULL) {
pixbuf = nautilus_ui_get_menu_icon (icon_name, parent_widget);
if (pixbuf != NULL) {
gtk_action_set_gicon (action, G_ICON (pixbuf));
g_object_unref (pixbuf);
}
}
g_return_if_fail (G_IS_MENU (menu));
g_return_if_fail (G_IS_MENU_ITEM (item));
gtk_action_set_sensitive (action, sensitive);
g_object_set (action, "is-important", priority, NULL);
submodel = find_gmenu_model (G_MENU_MODEL (menu), submodel_name);
g_signal_connect_data (action, "activate",
G_CALLBACK (extension_action_callback),
g_object_ref (item),
(GClosureNotify)g_object_unref, 0);
g_return_if_fail (submodel != NULL);
if (prepend)
g_menu_prepend_item (G_MENU (submodel), item);
else
g_menu_append_item (G_MENU (submodel), item);
}
void
nautilus_pop_up_context_menu (GtkWidget *parent,
GMenu *menu,
GdkEventButton *event)
{
GtkWidget *gtk_menu;
int button;
g_return_if_fail (G_IS_MENU (menu));
g_return_if_fail (GTK_IS_WIDGET (parent));
gtk_menu = gtk_menu_new_from_model (G_MENU_MODEL (menu));
gtk_menu_attach_to_widget (GTK_MENU (gtk_menu), parent, NULL);
/* The event button needs to be 0 if we're popping up this menu from
* a button release, else a 2nd click outside the menu with any button
* other than the one that invoked the menu will be ignored (instead
* of dismissing the menu). This is a subtle fragility of the GTK menu code.
*/
if (event) {
button = event->type == GDK_BUTTON_RELEASE
? 0
: event->button;
} else {
button = 0;
}
g_free (name);
g_free (label);
g_free (tip);
g_free (icon_name);
gtk_menu_popup (GTK_MENU (gtk_menu), /* menu */
NULL, /* parent_menu_shell */
NULL, /* parent_menu_item */
NULL, /* popup_position_func */
NULL, /* popup_position_data */
button, /* button */
event ? event->time : gtk_get_current_event_time ()); /* activate_time */
return action;
g_object_ref_sink (gtk_menu);
g_object_unref (gtk_menu);
}
GdkPixbuf *
......@@ -161,6 +220,18 @@ nautilus_escape_action_name (const char *action_name,
case '"':
g_string_append (s, "\\q");
break;
case ' ':
g_string_append (s, "+");
break;
case '(':
g_string_append (s, "#");
break;
case ')':
g_string_append (s, "^");
break;
case ':':
g_string_append (s, "\\\\");
break;
default:
g_string_append_c (s, *action_name);
}
......
......@@ -26,15 +26,18 @@
#include <gtk/gtk.h>
#include <libnautilus-extension/nautilus-menu-item.h>
void nautilus_ui_unmerge_ui (GtkUIManager *ui_manager,
guint *merge_id,
GtkActionGroup **action_group);
void nautilus_ui_prepare_merge_ui (GtkUIManager *ui_manager,
const char *name,
guint *merge_id,
GtkActionGroup **action_group);
GtkAction * nautilus_action_from_menu_item (NautilusMenuItem *item,
GtkWidget *parent_widget);
void nautilus_gmenu_add_item_in_submodel (GMenu *menu,
GMenuItem *item,
const gchar *section_name,
gboolean prepend);
void nautilus_gmenu_merge (GMenu *original,
GMenu *gmenu_to_merge,
const gchar *submodel_name,
gboolean prepend);
void nautilus_pop_up_context_menu (GtkWidget *parent,
GMenu *menu,
GdkEventButton *event);
GdkPixbuf * nautilus_ui_get_menu_icon (const char *icon_name,
GtkWidget *parent_widget);
......
......@@ -137,7 +137,6 @@ nautilus_built_sources = \
$(NULL)
nautilus_SOURCES = \
nautilus-actions.h \