/* * Copyright (c) 2012 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: Bastien Nocera * */ #include "totem-search-entry.h" #include "libgd/gd-tagged-entry.h" enum { SIGNAL_ACTIVATE, LAST_SIGNAL }; enum { PROP_0, PROP_SELECTED_ID }; static guint signals[LAST_SIGNAL] = { 0, }; struct _TotemSearchEntryPrivate { GtkWidget *entry; GtkWidget *popover; GtkWidget *listbox; GdTaggedEntryTag *tag; }; G_DEFINE_TYPE_WITH_PRIVATE (TotemSearchEntry, totem_search_entry, GTK_TYPE_BOX) static void totem_search_entry_finalize (GObject *obj) { TotemSearchEntry *self = TOTEM_SEARCH_ENTRY (obj); g_clear_object (&self->priv->tag); /* The popover will be destroyed with it parent (us) */ G_OBJECT_CLASS (totem_search_entry_parent_class)->finalize (obj); } static void entry_activate_cb (GtkEntry *entry, TotemSearchEntry *self) { const char *text; text = gtk_entry_get_text (GTK_ENTRY (self->priv->entry)); if (text == NULL || *text == '\0') return; g_signal_emit (self, signals[SIGNAL_ACTIVATE], 0); } static void tag_clicked_cb (GdTaggedEntry *entry, GdTaggedEntryTag *tag, TotemSearchEntry *self) { cairo_rectangle_int_t rect; if (gd_tagged_entry_tag_get_area (tag, &rect)) { gtk_popover_set_pointing_to (GTK_POPOVER (self->priv->popover), &rect); gtk_widget_show (self->priv->popover); } } static void listbox_row_activated (GtkListBox *list_box, GtkListBoxRow *row, gpointer user_data) { TotemSearchEntry *self = user_data; GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (list_box)); for (l = children; l != NULL; l = l->next) { GtkWidget *check; check = g_object_get_data (G_OBJECT (l->data), "check"); if (l->data == row) { const char *label; gtk_widget_set_opacity (check, 1.0); label = g_object_get_data (G_OBJECT (l->data), "label"); gd_tagged_entry_tag_set_label (self->priv->tag, label); g_object_notify (G_OBJECT (self), "selected-id"); } else { gtk_widget_set_opacity (check, 0.0); } } g_list_free (children); gtk_widget_hide (self->priv->popover); } static int sort_sources (GtkListBoxRow *row_a, GtkListBoxRow *row_b, gpointer user_data) { int prio_a, prio_b; const char *name_a, *name_b; prio_a = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row_a), "priority")); prio_b = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row_b), "priority")); if (prio_a > prio_b) return -1; if (prio_b > prio_a) return 1; name_a = g_object_get_data (G_OBJECT (row_a), "label"); name_b = g_object_get_data (G_OBJECT (row_b), "label"); return 0 - g_utf8_collate (name_a, name_b); } static void popover_closed_cb (GtkPopover *popover, TotemSearchEntry *self) { gtk_widget_grab_focus (self->priv->entry); } static void totem_search_entry_init (TotemSearchEntry *self) { GtkWidget *entry; self->priv = totem_search_entry_get_instance_private (self); /* Entry */ entry = GTK_WIDGET (gd_tagged_entry_new ()); gd_tagged_entry_set_tag_button_visible (GD_TAGGED_ENTRY (entry), FALSE); gtk_box_pack_start (GTK_BOX (self), entry, TRUE, TRUE, 0); gtk_widget_show (entry); self->priv->entry = entry; /* Popover */ self->priv->popover = gtk_popover_new (GTK_WIDGET (self)); gtk_popover_set_modal (GTK_POPOVER (self->priv->popover), TRUE); gtk_popover_set_position (GTK_POPOVER (self->priv->popover), GTK_POS_BOTTOM); g_signal_connect (G_OBJECT (self->priv->popover), "closed", G_CALLBACK (popover_closed_cb), self); self->priv->listbox = gtk_list_box_new (); gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (self->priv->listbox), TRUE); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->priv->listbox), sort_sources, self, NULL); gtk_widget_show (self->priv->listbox); gtk_container_add (GTK_CONTAINER (self->priv->popover), self->priv->listbox); g_signal_connect (self->priv->listbox, "row-activated", G_CALLBACK (listbox_row_activated), self); /* Connect signals */ g_signal_connect (self->priv->entry, "tag-clicked", G_CALLBACK (tag_clicked_cb), self); g_signal_connect (self->priv->entry, "activate", G_CALLBACK (entry_activate_cb), self); } static void totem_search_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec * pspec) { switch (property_id) { case PROP_SELECTED_ID: totem_search_entry_set_selected_id (TOTEM_SEARCH_ENTRY (object), g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void totem_search_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_SELECTED_ID: g_value_set_string (value, totem_search_entry_get_selected_id (TOTEM_SEARCH_ENTRY (object))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void totem_search_entry_class_init (TotemSearchEntryClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = totem_search_entry_finalize; gobject_class->set_property = totem_search_entry_set_property; gobject_class->get_property = totem_search_entry_get_property; signals[SIGNAL_ACTIVATE] = g_signal_new ("activate", TOTEM_TYPE_SEARCH_ENTRY, G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 0, G_TYPE_NONE); g_object_class_install_property (gobject_class, PROP_SELECTED_ID, g_param_spec_string ("selected-id", "Selected ID", "The ID for the currently selected source.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } TotemSearchEntry * totem_search_entry_new (void) { return g_object_new (TOTEM_TYPE_SEARCH_ENTRY, NULL); } static GtkWidget * padded_label_new (const char *text) { GtkWidget *widget; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); gtk_widget_set_margin_top (widget, 10); gtk_widget_set_margin_bottom (widget, 10); gtk_widget_set_margin_start (widget, 10); gtk_widget_set_margin_end (widget, 10); gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0); return widget; } void totem_search_entry_add_source (TotemSearchEntry *self, const gchar *id, const gchar *label, int priority) { GtkWidget *item; GtkWidget *check; GtkWidget *box; g_return_if_fail (TOTEM_IS_SEARCH_ENTRY (self)); if (self->priv->tag == NULL) { self->priv->tag = gd_tagged_entry_tag_new (label); gd_tagged_entry_tag_set_has_close_button (self->priv->tag, FALSE); gd_tagged_entry_insert_tag (GD_TAGGED_ENTRY (self->priv->entry), self->priv->tag, -1); gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE); } item = gtk_list_box_row_new (); box = padded_label_new (label); gtk_container_add (GTK_CONTAINER (item), box); check = gtk_image_new (); gtk_image_set_from_icon_name (GTK_IMAGE (check), "object-select-symbolic", GTK_ICON_SIZE_MENU); gtk_widget_set_opacity (check, 0.0); g_object_set (check, "icon-size", GTK_ICON_SIZE_MENU, NULL); gtk_box_pack_start (GTK_BOX (box), check, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (box), check, 0); g_object_set_data (G_OBJECT (item), "check", check); g_object_set_data_full (G_OBJECT (item), "id", g_strdup (id), g_free); g_object_set_data_full (G_OBJECT (item), "label", g_strdup (label), g_free); g_object_set_data (G_OBJECT (item), "priority", GINT_TO_POINTER (priority)); gtk_widget_show_all (item); gtk_list_box_insert (GTK_LIST_BOX (self->priv->listbox), item, -1); /* Is this the local one? */ if (priority == 50) { listbox_row_activated (GTK_LIST_BOX (self->priv->listbox), GTK_LIST_BOX_ROW (item), self); } } void totem_search_entry_remove_source (TotemSearchEntry *self, const gchar *id) { GList *children, *l; guint num_items; gboolean current_removed = FALSE; g_return_if_fail (TOTEM_IS_SEARCH_ENTRY (self)); children = gtk_container_get_children (GTK_CONTAINER (self->priv->listbox)); if (children == NULL) return; num_items = g_list_length (children) - 1; for (l = children; l != NULL; l = l->next) { const char *tmp_id; tmp_id = g_object_get_data (G_OBJECT (l->data), "id"); if (g_strcmp0 (id, tmp_id) == 0) { GtkWidget *check; check = g_object_get_data (G_OBJECT (l->data), "check"); if (gtk_widget_get_opacity (check) == 1.0) current_removed = TRUE; gtk_widget_destroy (l->data); } } if (current_removed) { if (!totem_search_entry_set_selected_id (self, "grl-tracker3-source")) totem_search_entry_set_selected_id (self, "grl-tracker-source"); } if (num_items == 0) { gd_tagged_entry_remove_tag (GD_TAGGED_ENTRY (self->priv->entry), self->priv->tag); g_clear_object (&self->priv->tag); gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); } } const char * totem_search_entry_get_text (TotemSearchEntry *self) { g_return_val_if_fail (TOTEM_IS_SEARCH_ENTRY (self), NULL); return gtk_entry_get_text (GTK_ENTRY (self->priv->entry)); } const char * totem_search_entry_get_selected_id (TotemSearchEntry *self) { GList *children, *l; const char *id = NULL; g_return_val_if_fail (TOTEM_IS_SEARCH_ENTRY (self), NULL); children = gtk_container_get_children (GTK_CONTAINER (self->priv->listbox)); for (l = children; l != NULL; l = l->next) { GtkWidget *check; check = g_object_get_data (G_OBJECT (l->data), "check"); if (gtk_widget_get_opacity (check) == 1.0) { id = g_object_get_data (G_OBJECT (l->data), "id"); break; } } g_list_free (children); return id; } gboolean totem_search_entry_set_selected_id (TotemSearchEntry *self, const char *id) { GList *children, *l; gboolean ret = FALSE; g_return_val_if_fail (TOTEM_IS_SEARCH_ENTRY (self), FALSE); g_return_val_if_fail (id != NULL, FALSE); children = gtk_container_get_children (GTK_CONTAINER (self->priv->listbox)); for (l = children; l != NULL; l = l->next) { const char *item_id; item_id = g_object_get_data (G_OBJECT (l->data), "id"); if (g_strcmp0 (item_id, id) == 0) { listbox_row_activated (GTK_LIST_BOX (self->priv->listbox), GTK_LIST_BOX_ROW (l->data), self); ret = TRUE; goto end; } } g_debug ("Could not find ID '%s' in TotemSearchEntry %p", id, self); end: g_list_free (children); return ret; } GtkEntry * totem_search_entry_get_entry (TotemSearchEntry *self) { g_return_val_if_fail (TOTEM_IS_SEARCH_ENTRY (self), NULL); return GTK_ENTRY (self->priv->entry); }