diff options
Diffstat (limited to 'subprojects/libgd/libgd/gd-main-icon-box.c')
-rw-r--r-- | subprojects/libgd/libgd/gd-main-icon-box.c | 1042 |
1 files changed, 1042 insertions, 0 deletions
diff --git a/subprojects/libgd/libgd/gd-main-icon-box.c b/subprojects/libgd/libgd/gd-main-icon-box.c new file mode 100644 index 0000000..1d6cdf4 --- /dev/null +++ b/subprojects/libgd/libgd/gd-main-icon-box.c @@ -0,0 +1,1042 @@ +/* + * Copyright (c) 2016, 2017 Red Hat, Inc. + * + * This program 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 of the License, or (at your + * option) any later version. + * + * This program 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 Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Debarshi Ray <debarshir@gnome.org> + * + */ + +#include <math.h> + +#include <cairo.h> +#include <gio/gio.h> + +#include "gd-icon-utils.h" +#include "gd-main-icon-box.h" +#include "gd-main-icon-box-child.h" +#include "gd-main-box-child.h" +#include "gd-main-box-generic.h" +#include "gd-main-box-item.h" + +#define MAIN_ICON_BOX_DND_ICON_OFFSET 20 + +typedef struct _GdMainIconBoxPrivate GdMainIconBoxPrivate; + +struct _GdMainIconBoxPrivate +{ + GListModel *model; + gboolean dnd_started; + gboolean key_pressed; + gboolean key_shift_pressed; + gboolean left_button_released; + gboolean left_button_shift_released; + gboolean selection_changed; + gboolean selection_mode; + gboolean show_primary_text; + gboolean show_secondary_text; + gchar *last_selected_id; + gdouble dnd_start_x; + gdouble dnd_start_y; + gint dnd_button; +}; + +enum +{ + PROP_LAST_SELECTED_ID = 1, + PROP_MODEL, + PROP_SELECTION_MODE, + PROP_SHOW_PRIMARY_TEXT, + PROP_SHOW_SECONDARY_TEXT, + NUM_PROPERTIES +}; + +static void gd_main_box_generic_interface_init (GdMainBoxGenericInterface *iface); +G_DEFINE_TYPE_WITH_CODE (GdMainIconBox, gd_main_icon_box, GTK_TYPE_FLOW_BOX, + G_ADD_PRIVATE (GdMainIconBox) + G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_BOX_GENERIC, gd_main_box_generic_interface_init)) + +GtkWidget * +gd_main_icon_box_create_widget_func (gpointer item, gpointer user_data) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (user_data); + GdMainIconBoxPrivate *priv; + GtkWidget *child; + + g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (item), NULL); + + priv = gd_main_icon_box_get_instance_private (self); + + child = gd_main_icon_box_child_new (GD_MAIN_BOX_ITEM (item), priv->selection_mode); + g_object_bind_property (self, "show-primary-text", child, "show-primary-text", G_BINDING_SYNC_CREATE); + g_object_bind_property (self, "show-secondary-text", child, "show-secondary-text", G_BINDING_SYNC_CREATE); + gtk_widget_show_all (child); + + return child; +} + +static void +gd_main_icon_box_update_last_selected_id (GdMainIconBox *self, GdMainBoxChild *child) +{ + GdMainIconBoxPrivate *priv; + GdMainBoxItem *item; + const gchar *id = NULL; + + priv = gd_main_icon_box_get_instance_private (self); + + if (child != NULL) + { + item = gd_main_box_child_get_item (child); + id = gd_main_box_item_get_id (item); + } + + if (g_strcmp0 (priv->last_selected_id, id) != 0) + { + g_free (priv->last_selected_id); + priv->last_selected_id = g_strdup (id); + g_object_notify (G_OBJECT (self), "last-selected-id"); + } +} + +static GdMainBoxChild * +gd_main_icon_box_get_child_at_index (GdMainBoxGeneric *generic, gint index) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + GtkFlowBoxChild *child; + + child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (self), index); + return GD_MAIN_BOX_CHILD (child); +} + +static const gchar * +gd_main_icon_box_get_last_selected_id (GdMainBoxGeneric *generic) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + return priv->last_selected_id; +} + +static GListModel * +gd_main_icon_box_get_model (GdMainBoxGeneric *generic) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + return priv->model; +} + +static GList * +gd_main_icon_box_get_selected_children (GdMainBoxGeneric *generic) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + GList *selected_children; + + selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self)); + return selected_children; +} + +static gboolean +gd_main_icon_box_get_selection_mode (GdMainIconBox *self) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + return priv->selection_mode; +} + +static gboolean +gd_main_icon_box_get_show_primary_text (GdMainIconBox *self) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + return priv->show_primary_text; +} + +static gboolean +gd_main_icon_box_get_show_secondary_text (GdMainIconBox *self) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + return priv->show_secondary_text; +} + +static void +gd_main_icon_box_select_all_generic (GdMainBoxGeneric *generic) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + g_signal_emit_by_name (self, "select-all"); +} + +static void +gd_main_icon_box_select_child (GdMainBoxGeneric *generic, GdMainBoxChild *child) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + gtk_flow_box_select_child (GTK_FLOW_BOX (self), GTK_FLOW_BOX_CHILD (child)); +} + +static void +gd_main_icon_box_set_model (GdMainIconBox *self, GListModel *model) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + if (!g_set_object (&priv->model, model)) + return; + + gtk_flow_box_bind_model (GTK_FLOW_BOX (self), + priv->model, + gd_main_icon_box_create_widget_func, + self, + NULL); + + g_object_notify (G_OBJECT (self), "model"); +} + +static void +gd_main_icon_box_set_selection_mode (GdMainIconBox *self, gboolean selection_mode) +{ + GdMainIconBoxPrivate *priv; + GList *children; + GList *l; + + priv = gd_main_icon_box_get_instance_private (self); + + if (priv->selection_mode == selection_mode) + return; + + gd_main_icon_box_update_last_selected_id (self, NULL); + + priv->selection_mode = selection_mode; + if (priv->selection_mode) + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_MULTIPLE); + else + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_NONE); + + children = gtk_container_get_children (GTK_CONTAINER (self)); + for (l = children; l != NULL; l = l->next) + { + GdMainBoxChild *child = GD_MAIN_BOX_CHILD (l->data); + gd_main_box_child_set_selection_mode (child, priv->selection_mode); + } + + g_object_notify (G_OBJECT (self), "last-selected-id"); + g_object_notify (G_OBJECT (self), "selection-mode"); + + g_list_free (children); +} + +static void +gd_main_icon_box_set_show_primary_text (GdMainIconBox *self, gboolean show_primary_text) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + if (priv->show_primary_text == show_primary_text) + return; + + priv->show_primary_text = show_primary_text; + g_object_notify (G_OBJECT (self), "show-primary-text"); +} + +static void +gd_main_icon_box_set_show_secondary_text (GdMainIconBox *self, gboolean show_secondary_text) +{ + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + if (priv->show_secondary_text == show_secondary_text) + return; + + priv->show_secondary_text = show_secondary_text; + g_object_notify (G_OBJECT (self), "show-secondary-text"); +} + +static void +gd_main_icon_box_unselect_all_generic (GdMainBoxGeneric *generic) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + g_signal_emit_by_name (self, "unselect-all"); +} + +static void +gd_main_icon_box_unselect_child (GdMainBoxGeneric *generic, GdMainBoxChild *child) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (generic); + gtk_flow_box_unselect_child (GTK_FLOW_BOX (self), GTK_FLOW_BOX_CHILD (child)); +} + +static void +gd_main_icon_box_activate_cursor_child (GtkFlowBox *flow_box) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + GdkEvent *event = NULL; + gboolean initiating = FALSE; + + priv = gd_main_icon_box_get_instance_private (self); + + /* Use GtkFlowBox::activate-cursor-child instead of + * GtkWidget::key-press-event to catch key presses because it is + * easier to filter out non-activation keys. + */ + + event = gtk_get_current_event (); + if (event == NULL) + goto out; + + if (event->type != GDK_KEY_PRESS) + goto out; + + if (!priv->selection_mode && (event->key.state & GDK_CONTROL_MASK) != 0) + { + g_signal_emit_by_name (self, "selection-mode-request"); + initiating = TRUE; + } + + if (priv->selection_mode) + { + if (!initiating && (event->key.state & GDK_SHIFT_MASK) != 0) + priv->key_shift_pressed = TRUE; + + priv->key_pressed = TRUE; + } + + out: + GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->activate_cursor_child (flow_box); + g_clear_pointer (&event, gdk_event_free); +} + +static gboolean +gd_main_icon_box_button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GtkFlowBoxChild *child; + gboolean res; + + priv = gd_main_icon_box_get_instance_private (self); + + if (event->type != GDK_BUTTON_PRESS) + { + res = GDK_EVENT_STOP; + goto out; + } + + if (event->button != GDK_BUTTON_PRIMARY) + goto default_behavior; + + child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), (gint) event->x, (gint) event->y); + if (child == NULL) + goto default_behavior; + + if (priv->selection_mode && !gtk_flow_box_child_is_selected (child)) + goto default_behavior; + + priv->dnd_button = (gint) event->button; + priv->dnd_start_x = event->x; + priv->dnd_start_y = event->y; + + default_behavior: + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->button_press_event (widget, event); + + out: + return res; +} + +static gboolean +gd_main_icon_box_button_release_event (GtkWidget *widget, GdkEventButton *event) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GtkFlowBoxChild *child = NULL; + gboolean initiating = FALSE; + gboolean res; + + priv = gd_main_icon_box_get_instance_private (self); + + priv->dnd_button = -1; + priv->dnd_start_x = -1.0; + priv->dnd_start_y = -1.0; + priv->dnd_started = FALSE; + + if (event->type != GDK_BUTTON_RELEASE) + { + res = GDK_EVENT_STOP; + goto out; + } + + if (!priv->selection_mode && + ((event->button == GDK_BUTTON_PRIMARY && (event->state & GDK_CONTROL_MASK) != 0) || + event->button == GDK_BUTTON_SECONDARY)) + { + g_signal_emit_by_name (self, "selection-mode-request"); + initiating = TRUE; + } + + if (priv->selection_mode) + { + if (event->button == GDK_BUTTON_PRIMARY) + { + /* GtkFlowBox doesn't do range selection. It will simply + * select a single child for shift + left-click. We need to + * detect it so that we can handle it later. + * + * However, range selection is only possible if we were + * already in the selection mode. Therefore, skip it if we + * have just requested the selection mode. + */ + if (!initiating && (event->state & GDK_SHIFT_MASK) != 0) + priv->left_button_shift_released = TRUE; + + priv->left_button_released = TRUE; + } + else if (event->button == GDK_BUTTON_SECONDARY) + { + /* GtkFlowBox completely ignores the right mouse + * button. + */ + + child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), (gint) event->x, (gint) event->y); + if (child != NULL) + { + gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), + GD_MAIN_BOX_CHILD (child), + (!initiating && + (event->state & GDK_SHIFT_MASK) != 0)); + } + } + } + + /* This is for right-clicks and rubberband selection. + * + * Rubberband selection is unlike other modes of selection because + * GtkFlowBox::selected-children-changed is emitted before the mouse + * button is released. + */ + if (priv->selection_changed) + { + g_signal_emit_by_name (self, "selection-changed"); + gd_main_icon_box_update_last_selected_id (self, GD_MAIN_BOX_CHILD (child)); + priv->selection_changed = FALSE; + } + + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->button_release_event (widget, event); + + out: + return res; +} + +static void +gd_main_icon_box_child_activated (GtkFlowBox *flow_box, GtkFlowBoxChild *child) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + GdkEvent *event = NULL; + + g_return_if_fail (GD_IS_MAIN_BOX_CHILD (child)); + + priv = gd_main_icon_box_get_instance_private (self); + + /* GtkFlowBox might emit child-activated in the middle of a + * DnD. See https://bugzilla.gnome.org/show_bug.cgi?id=776306 + */ + if (priv->dnd_started) + goto out; + + if (!priv->selection_mode) + { + g_signal_emit_by_name (self, "item-activated", GD_MAIN_BOX_CHILD (child)); + goto out; + } + + event = gtk_get_current_event (); + if (event == NULL) + goto out; + + if (priv->left_button_released && !priv->selection_changed) + { + /* If a selected child is left-clicked, GtkFlowBox will activate + * it without unselecting it. + */ + gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), + GD_MAIN_BOX_CHILD (child), + FALSE); /* One cannot unselect a range. */ + priv->left_button_released = FALSE; + g_signal_emit_by_name (self, "selection-changed"); + } + else if (priv->key_pressed && !priv->selection_changed) + { + /* If a selected child is activated by a keybinding, GtkFlowBox + * will not unselect it. + */ + gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), FALSE); + priv->key_pressed = FALSE; + g_signal_emit_by_name (self, "selection-changed"); + } + else if (priv->left_button_shift_released || priv->key_shift_pressed) + { + /* GtkFlowBox doesn't do range selection and simply selects a + * single child. We handle it by unselecting the child and then + * selecting the range. + */ + gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), FALSE); + priv->left_button_shift_released = FALSE; + priv->key_shift_pressed = FALSE; + gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), TRUE); + g_signal_emit_by_name (self, "selection-changed"); + } + else if (priv->selection_changed) + { + /* This is for non-shift left-clicks and keyboard activation of + * unselected children. + */ + g_signal_emit_by_name (self, "selection-changed"); + } + + g_signal_emit_by_name (self, "item-activated", GD_MAIN_BOX_CHILD (child)); + + if (priv->selection_changed) + { + gd_main_icon_box_update_last_selected_id (self, GD_MAIN_BOX_CHILD (child)); + priv->selection_changed = FALSE; + } + + out: + g_clear_pointer (&event, gdk_event_free); +} + +static void +gd_main_icon_box_drag_begin (GtkWidget *widget, GdkDragContext *context) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GdMainBoxItem *item; + GtkFlowBoxChild *child; + cairo_surface_t *drag_icon = NULL; + cairo_surface_t *icon; + + priv = gd_main_icon_box_get_instance_private (self); + + if (priv->dnd_start_x < 0.0 || priv->dnd_start_y < 0.0) + goto out; + + child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), + (gint) priv->dnd_start_x, + (gint) priv->dnd_start_y); + if (child == NULL) + goto out; + + item = gd_main_box_child_get_item (GD_MAIN_BOX_CHILD (child)); + icon = gd_main_box_item_get_icon (item); + if (icon == NULL) + goto out; + + if (priv->selection_mode) + { + GList *selected_children; + guint length; + + selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self)); + length = g_list_length (selected_children); + if (length > 1) + drag_icon = gd_create_surface_with_counter (GTK_WIDGET (self), icon, length); + + g_list_free (selected_children); + } + + if (drag_icon == NULL) + drag_icon = gd_copy_image_surface (icon); + + cairo_surface_set_device_offset (drag_icon, -MAIN_ICON_BOX_DND_ICON_OFFSET, -MAIN_ICON_BOX_DND_ICON_OFFSET); + gtk_drag_set_icon_surface (context, drag_icon); + + out: + g_clear_pointer (&drag_icon, cairo_surface_destroy); +} + +static void +gd_main_icon_box_add_child_uri_to_array (GdMainBoxChild *child, GPtrArray *uri_array) +{ + GdMainBoxItem *item; + const gchar *uri; + + item = gd_main_box_child_get_item (child); + uri = gd_main_box_item_get_uri (item); + g_ptr_array_add (uri_array, g_strdup (uri)); +} + +static void +gd_main_icon_box_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *data, + guint info, + guint time) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GPtrArray *uri_array = NULL; + + priv = gd_main_icon_box_get_instance_private (self); + + if (info != 0) + goto out; + + if (priv->dnd_start_x < 0.0 || priv->dnd_start_y < 0.0) + goto out; + + uri_array = g_ptr_array_new_with_free_func (g_free); + + if (priv->selection_mode) + { + GList *l; + GList *selected_children; + + selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self)); + for (l = selected_children; l != NULL; l = l->next) + { + GdMainBoxChild *child = GD_MAIN_BOX_CHILD (l->data); + gd_main_icon_box_add_child_uri_to_array (child, uri_array); + } + + g_list_free (selected_children); + } + else + { + GtkFlowBoxChild *child; + + child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), + (gint) priv->dnd_start_x, + (gint) priv->dnd_start_y); + + if (child != NULL) + gd_main_icon_box_add_child_uri_to_array (GD_MAIN_BOX_CHILD (child), uri_array); + } + + g_ptr_array_add (uri_array, NULL); + gtk_selection_data_set_uris (data, (gchar **) uri_array->pdata); + + out: + g_clear_pointer (&uri_array, g_ptr_array_unref); +} + +static gboolean +gd_main_icon_box_focus (GtkWidget *widget, GtkDirectionType direction) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GdkEvent *event = NULL; + GdkEvent *fake_event = NULL; + gboolean res; + + priv = gd_main_icon_box_get_instance_private (self); + + if (!priv->selection_mode) + { + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction); + goto out; + } + + event = gtk_get_current_event (); + if (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE) + { + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction); + goto out; + } + + if ((event->key.state & GDK_CONTROL_MASK) != 0) + { + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction); + goto out; + } + + fake_event = gdk_event_copy (event); + fake_event->key.state |= GDK_CONTROL_MASK; + + gtk_main_do_event (fake_event); + res = GDK_EVENT_STOP; + + out: + g_clear_pointer (&fake_event, gdk_event_free); + g_clear_pointer (&event, gdk_event_free); + return res; +} + +static gboolean +gd_main_icon_box_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (widget); + GdMainIconBoxPrivate *priv; + GtkTargetList *targets; + gboolean res; + gint button; + + priv = gd_main_icon_box_get_instance_private (self); + + if (priv->dnd_button < 0) + goto out; + + if (!gtk_drag_check_threshold (GTK_WIDGET (self), + (gint) priv->dnd_start_x, + (gint) priv->dnd_start_y, + (gint) event->x, + (gint) event->y)) + goto out; + + button = priv->dnd_button; + priv->dnd_button = -1; + priv->dnd_started = TRUE; + + targets = gtk_drag_source_get_target_list (GTK_WIDGET (self)); + + gtk_drag_begin_with_coordinates (GTK_WIDGET (self), + targets, + GDK_ACTION_COPY, + button, + (GdkEvent *) event, + (gint) priv->dnd_start_x, + (gint) priv->dnd_start_y); + + out: + res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->motion_notify_event (widget, event); + return res; +} + +static gboolean +gd_main_icon_box_move_cursor (GtkFlowBox *flow_box, GtkMovementStep step, gint count) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + GdkEvent *event = NULL; + GdkEvent *fake_event = NULL; + gboolean res; + + priv = gd_main_icon_box_get_instance_private (self); + + if (!priv->selection_mode) + { + res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count); + goto out; + } + + event = gtk_get_current_event (); + if (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE) + { + res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count); + goto out; + } + + if ((event->key.state & GDK_CONTROL_MASK) != 0 && (event->key.state & GDK_SHIFT_MASK) == 0) + { + res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count); + goto out; + } + + fake_event = gdk_event_copy (event); + fake_event->key.state |= GDK_CONTROL_MASK; + fake_event->key.state &= ~GDK_SHIFT_MASK; + + gtk_main_do_event (fake_event); + res = GDK_EVENT_STOP; + + out: + g_clear_pointer (&fake_event, gdk_event_free); + g_clear_pointer (&event, gdk_event_free); + return res; +} + +static void +gd_main_icon_box_remove (GtkContainer *container, GtkWidget *widget) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (container); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + GTK_CONTAINER_CLASS (gd_main_icon_box_parent_class)->remove (container, widget); + + if (priv->selection_changed) + { + g_signal_emit_by_name (self, "selection-changed"); + priv->selection_changed = FALSE; + } +} + +static void +gd_main_icon_box_select_all_flow_box (GtkFlowBox *flow_box) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->select_all (flow_box); + + if (priv->selection_changed) + { + g_signal_emit_by_name (self, "selection-changed"); + priv->selection_changed = FALSE; + } +} + +static void +gd_main_icon_box_selected_children_changed (GtkFlowBox *flow_box) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->selected_children_changed (flow_box); + + priv->selection_changed = TRUE; + + /* When a range selection is attempted, we override GtkFlowBox's + * default behaviour by changing the selection ourselves. Therefore, + * there is no need to update the check buttons until the final + * selection is available. + */ + if (!priv->key_shift_pressed && !priv->left_button_shift_released) + { + GList *children; + GList *l; + + children = gtk_container_get_children (GTK_CONTAINER (self)); + for (l = children; l != NULL; l = l->next) + { + GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (l->data); + gboolean selected; + + /* Work around the fact that GtkFlowBoxChild:selected is not + * a property. + */ + selected = gtk_flow_box_child_is_selected (child); + gd_main_box_child_set_selected (GD_MAIN_BOX_CHILD (child), selected); + } + + g_list_free (children); + } +} + +static void +gd_main_icon_box_unselect_all_flow_box (GtkFlowBox *flow_box) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->unselect_all (flow_box); + + if (priv->selection_changed) + { + g_signal_emit_by_name (self, "selection-changed"); + priv->selection_changed = FALSE; + } +} + +static void +gd_main_icon_box_dispose (GObject *obj) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (obj); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + g_clear_object (&priv->model); + + G_OBJECT_CLASS (gd_main_icon_box_parent_class)->dispose (obj); +} + +static void +gd_main_icon_box_finalize (GObject *obj) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (obj); + GdMainIconBoxPrivate *priv; + + priv = gd_main_icon_box_get_instance_private (self); + + g_free (priv->last_selected_id); + + G_OBJECT_CLASS (gd_main_icon_box_parent_class)->finalize (obj); +} + +static void +gd_main_icon_box_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (object); + + switch (property_id) + { + case PROP_LAST_SELECTED_ID: + g_value_set_string (value, gd_main_icon_box_get_last_selected_id (GD_MAIN_BOX_GENERIC (self))); + break; + case PROP_MODEL: + g_value_set_object (value, gd_main_icon_box_get_model (GD_MAIN_BOX_GENERIC (self))); + break; + case PROP_SELECTION_MODE: + g_value_set_boolean (value, gd_main_icon_box_get_selection_mode (self)); + break; + case PROP_SHOW_PRIMARY_TEXT: + g_value_set_boolean (value, gd_main_icon_box_get_show_primary_text (self)); + break; + case PROP_SHOW_SECONDARY_TEXT: + g_value_set_boolean (value, gd_main_icon_box_get_show_secondary_text (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_main_icon_box_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) +{ + GdMainIconBox *self = GD_MAIN_ICON_BOX (object); + + switch (property_id) + { + case PROP_MODEL: + gd_main_icon_box_set_model (self, g_value_get_object (value)); + break; + case PROP_SELECTION_MODE: + gd_main_icon_box_set_selection_mode (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_PRIMARY_TEXT: + gd_main_icon_box_set_show_primary_text (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_SECONDARY_TEXT: + gd_main_icon_box_set_show_secondary_text (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_main_icon_box_init (GdMainIconBox *self) +{ + GdMainIconBoxPrivate *priv; + const GtkTargetEntry targets[] = { { (gchar *) "text/uri-list", GTK_TARGET_OTHER_APP, 0 } }; + + priv = gd_main_icon_box_get_instance_private (self); + + gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); + gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (self), TRUE); + gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (self), 3); + gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_NONE); + + /* We need to ensure that rubberband selection and DnD don't step + * on each others toes. We set start_button_mask to 0 to retain + * control over when to begin a drag. + */ + gtk_drag_source_set (GTK_WIDGET (self), 0, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY); + + priv->dnd_button = -1; + priv->dnd_start_x = -1.0; + priv->dnd_start_y = -1.0; +} + +static void +gd_main_icon_box_class_init (GdMainIconBoxClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + GtkContainerClass *cclass = GTK_CONTAINER_CLASS (klass); + GtkFlowBoxClass *fbclass = GTK_FLOW_BOX_CLASS (klass); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); + GtkBindingSet *binding_set; + GdkModifierType activate_modifiers[] = { 0, /* Otherwise it will go to GtkFlowBoxChild::activate. */ + GDK_SHIFT_MASK, + GDK_CONTROL_MASK, + GDK_SHIFT_MASK | GDK_CONTROL_MASK }; + guint i; + + binding_set = gtk_binding_set_by_class (klass); + + oclass->dispose = gd_main_icon_box_dispose; + oclass->finalize = gd_main_icon_box_finalize; + oclass->get_property = gd_main_icon_box_get_property; + oclass->set_property = gd_main_icon_box_set_property; + wclass->button_press_event = gd_main_icon_box_button_press_event; + wclass->button_release_event = gd_main_icon_box_button_release_event; + wclass->drag_begin = gd_main_icon_box_drag_begin; + wclass->drag_data_get = gd_main_icon_box_drag_data_get; + wclass->focus = gd_main_icon_box_focus; + wclass->motion_notify_event = gd_main_icon_box_motion_notify_event; + cclass->remove = gd_main_icon_box_remove; + fbclass->activate_cursor_child = gd_main_icon_box_activate_cursor_child; + fbclass->child_activated = gd_main_icon_box_child_activated; + fbclass->move_cursor = gd_main_icon_box_move_cursor; + fbclass->select_all = gd_main_icon_box_select_all_flow_box; + fbclass->selected_children_changed = gd_main_icon_box_selected_children_changed; + fbclass->unselect_all = gd_main_icon_box_unselect_all_flow_box; + + g_object_class_override_property (oclass, PROP_LAST_SELECTED_ID, "last-selected-id"); + g_object_class_override_property (oclass, PROP_MODEL, "model"); + g_object_class_override_property (oclass, PROP_SELECTION_MODE, "gd-selection-mode"); + g_object_class_override_property (oclass, PROP_SHOW_PRIMARY_TEXT, "show-primary-text"); + g_object_class_override_property (oclass, PROP_SHOW_SECONDARY_TEXT, "show-secondary-text"); + + for (i = 0; i < G_N_ELEMENTS (activate_modifiers); i++) + { + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_space, activate_modifiers[i], + "activate-cursor-child", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_KP_Space, activate_modifiers[i], + "activate-cursor-child", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_Return, activate_modifiers[i], + "activate-cursor-child", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_ISO_Enter, activate_modifiers[i], + "activate-cursor-child", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KEY_KP_Enter, activate_modifiers[i], + "activate-cursor-child", + 0); + } +} + +static void +gd_main_box_generic_interface_init (GdMainBoxGenericInterface *iface) +{ + iface->get_child_at_index = gd_main_icon_box_get_child_at_index; + iface->get_last_selected_id = gd_main_icon_box_get_last_selected_id; + iface->get_model = gd_main_icon_box_get_model; + iface->get_selected_children = gd_main_icon_box_get_selected_children; + iface->select_all = gd_main_icon_box_select_all_generic; + iface->select_child = gd_main_icon_box_select_child; + iface->unselect_all = gd_main_icon_box_unselect_all_generic; + iface->unselect_child = gd_main_icon_box_unselect_child; +} + +GtkWidget * +gd_main_icon_box_new (void) +{ + return g_object_new (GD_TYPE_MAIN_ICON_BOX, NULL); +} |