842 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			842 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  | /* PIKA - Photo and Image Kooker Application
 | ||
|  |  * a rebranding of The GNU Image Manipulation Program (created with heckimp) | ||
|  |  * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio | ||
|  |  * | ||
|  |  * Original copyright, applying to most contents (license remains unchanged):  | ||
|  |  * Copyright (C) 1995 Spencer Kimball and Peter Mattis | ||
|  |  * | ||
|  |  * pikasearchpopup.c | ||
|  |  * Copyright (C) 2015 Jehan <jehan at girinstud.io> | ||
|  |  * | ||
|  |  * This program is free software: you can redistribute it and/or modify | ||
|  |  * it under the terms of the GNU General Public License as published by | ||
|  |  * the Free Software Foundation; either version 3 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 General Public License for more details. | ||
|  |  * | ||
|  |  * You should have received a copy of the GNU General Public License | ||
|  |  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "config.h"
 | ||
|  | 
 | ||
|  | #include <gegl.h>
 | ||
|  | #include <gtk/gtk.h>
 | ||
|  | 
 | ||
|  | #include <gdk/gdkkeysyms.h>
 | ||
|  | 
 | ||
|  | #include "libpikabase/pikabase.h"
 | ||
|  | #include "libpikawidgets/pikawidgets.h"
 | ||
|  | 
 | ||
|  | #include "widgets-types.h"
 | ||
|  | 
 | ||
|  | #include "core/pika.h"
 | ||
|  | 
 | ||
|  | #include "pikaaction.h"
 | ||
|  | #include "pikahelp-ids.h"
 | ||
|  | #include "pikapopup.h"
 | ||
|  | #include "pikasearchpopup.h"
 | ||
|  | #include "pikatoggleaction.h"
 | ||
|  | 
 | ||
|  | #include "pika-intl.h"
 | ||
|  | 
 | ||
|  | 
 | ||
|  | enum | ||
|  | { | ||
|  |   COLUMN_ICON, | ||
|  |   COLUMN_MARKUP, | ||
|  |   COLUMN_TOOLTIP, | ||
|  |   COLUMN_ACTION, | ||
|  |   COLUMN_SENSITIVE, | ||
|  |   COLUMN_SECTION, | ||
|  |   N_COL | ||
|  | }; | ||
|  | 
 | ||
|  | enum | ||
|  | { | ||
|  |   PROP_0, | ||
|  |   PROP_PIKA, | ||
|  |   PROP_CALLBACK, | ||
|  |   PROP_CALLBACK_DATA | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | struct _PikaSearchPopupPrivate | ||
|  | { | ||
|  |   Pika                    *pika; | ||
|  |   GtkWidget               *keyword_entry; | ||
|  |   GtkWidget               *results_list; | ||
|  |   GtkWidget               *list_view; | ||
|  | 
 | ||
|  |   PikaSearchPopupCallback  build_results; | ||
|  |   gpointer                 build_results_data; | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | static void       pika_search_popup_constructed         (GObject            *object); | ||
|  | static void       pika_search_popup_set_property        (GObject            *object, | ||
|  |                                                          guint               property_id, | ||
|  |                                                          const GValue       *value, | ||
|  |                                                          GParamSpec         *pspec); | ||
|  | static void       pika_search_popup_get_property        (GObject            *object, | ||
|  |                                                          guint               property_id, | ||
|  |                                                          GValue             *value, | ||
|  |                                                          GParamSpec         *pspec); | ||
|  | 
 | ||
|  | static void       pika_search_popup_size_allocate        (GtkWidget         *widget, | ||
|  |                                                           GtkAllocation     *allocation); | ||
|  | 
 | ||
|  | static void       pika_search_popup_confirm              (PikaPopup *popup); | ||
|  | 
 | ||
|  | /* Signal handlers on the search entry */ | ||
|  | static void       keyword_entry_icon_press               (GtkEntry             *entry, | ||
|  |                                                           GtkEntryIconPosition  icon_pos, | ||
|  |                                                           GdkEvent             *event, | ||
|  |                                                           PikaSearchPopup      *popup); | ||
|  | static gboolean   keyword_entry_key_press_event          (GtkWidget            *widget, | ||
|  |                                                           GdkEventKey          *event, | ||
|  |                                                           PikaSearchPopup      *popup); | ||
|  | static gboolean   keyword_entry_key_release_event        (GtkWidget            *widget, | ||
|  |                                                           GdkEventKey          *event, | ||
|  |                                                           PikaSearchPopup      *popup); | ||
|  | 
 | ||
|  | /* Signal handlers on the results list */ | ||
|  | static gboolean   results_list_key_press_event           (GtkWidget         *widget, | ||
|  |                                                           GdkEventKey       *kevent, | ||
|  |                                                           PikaSearchPopup   *popup); | ||
|  | static void       results_list_row_activated             (GtkTreeView       *treeview, | ||
|  |                                                           GtkTreePath       *path, | ||
|  |                                                           GtkTreeViewColumn *col, | ||
|  |                                                           PikaSearchPopup   *popup); | ||
|  | 
 | ||
|  | /* Utils */ | ||
|  | static void       pika_search_popup_run_selected         (PikaSearchPopup   *popup); | ||
|  | static void       pika_search_popup_setup_results        (GtkWidget        **results_list, | ||
|  |                                                           GtkWidget        **list_view); | ||
|  | static void       pika_search_popup_help                 (PikaSearchPopup   *popup); | ||
|  | 
 | ||
|  | 
 | ||
|  | G_DEFINE_TYPE_WITH_PRIVATE (PikaSearchPopup, pika_search_popup, PIKA_TYPE_POPUP) | ||
|  | 
 | ||
|  | #define parent_class pika_search_popup_parent_class
 | ||
|  | 
 | ||
|  | static gint window_height = 0; | ||
|  | 
 | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_class_init (PikaSearchPopupClass *klass) | ||
|  | { | ||
|  |   GObjectClass   *object_class = G_OBJECT_CLASS (klass); | ||
|  |   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); | ||
|  |   PikaPopupClass *popup_class  = PIKA_POPUP_CLASS (klass); | ||
|  | 
 | ||
|  |   object_class->constructed   = pika_search_popup_constructed; | ||
|  |   object_class->set_property  = pika_search_popup_set_property; | ||
|  |   object_class->get_property  = pika_search_popup_get_property; | ||
|  | 
 | ||
|  |   widget_class->size_allocate = pika_search_popup_size_allocate; | ||
|  | 
 | ||
|  |   popup_class->confirm        = pika_search_popup_confirm; | ||
|  | 
 | ||
|  |   /**
 | ||
|  |    * PikaSearchPopup:pika: | ||
|  |    * | ||
|  |    * The #Pika object. | ||
|  |    */ | ||
|  |   g_object_class_install_property (object_class, PROP_PIKA, | ||
|  |                                    g_param_spec_object ("pika", | ||
|  |                                                         NULL, NULL, | ||
|  |                                                         G_TYPE_OBJECT, | ||
|  |                                                         G_PARAM_READWRITE | | ||
|  |                                                         G_PARAM_CONSTRUCT_ONLY)); | ||
|  |   /**
 | ||
|  |    * PikaSearchPopup:callback: | ||
|  |    * | ||
|  |    * The #PikaSearchPopupCallback used to fill in results. | ||
|  |    */ | ||
|  |   g_object_class_install_property (object_class, PROP_CALLBACK, | ||
|  |                                    g_param_spec_pointer ("callback", NULL, NULL, | ||
|  |                                                          PIKA_PARAM_READWRITE | | ||
|  |                                                          G_PARAM_CONSTRUCT_ONLY)); | ||
|  |   /**
 | ||
|  |    * PikaSearchPopup:callback-data: | ||
|  |    * | ||
|  |    * The #GPointer fed as last parameter to the #PikaSearchPopupCallback. | ||
|  |    */ | ||
|  |   g_object_class_install_property (object_class, PROP_CALLBACK_DATA, | ||
|  |                                    g_param_spec_pointer ("callback-data", NULL, NULL, | ||
|  |                                                          PIKA_PARAM_READWRITE | | ||
|  |                                                          G_PARAM_CONSTRUCT_ONLY)); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_init (PikaSearchPopup *search_popup) | ||
|  | { | ||
|  |   search_popup->priv = pika_search_popup_get_instance_private (search_popup); | ||
|  | } | ||
|  | 
 | ||
|  | /************ Public Functions ****************/ | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * pika_search_popup_new: | ||
|  |  * @pika:          #Pika object. | ||
|  |  * @role:          the role to give to the #GtkWindow. | ||
|  |  * @title:         the #GtkWindow title. | ||
|  |  * @callback:      the #PikaSearchPopupCallback used to fill in results. | ||
|  |  * @callback_data: data fed to @callback. | ||
|  |  * | ||
|  |  * Returns: a new #PikaSearchPopup. | ||
|  |  */ | ||
|  | GtkWidget * | ||
|  | pika_search_popup_new (Pika                    *pika, | ||
|  |                        const gchar             *role, | ||
|  |                        const gchar             *title, | ||
|  |                        PikaSearchPopupCallback  callback, | ||
|  |                        gpointer                 callback_data) | ||
|  | { | ||
|  |   GtkWidget *widget; | ||
|  | 
 | ||
|  |   widget = g_object_new (PIKA_TYPE_SEARCH_POPUP, | ||
|  |                          "type",          GTK_WINDOW_TOPLEVEL, | ||
|  |                          "type-hint",     GDK_WINDOW_TYPE_HINT_DIALOG, | ||
|  |                          "decorated",     TRUE, | ||
|  |                          "modal",         TRUE, | ||
|  |                          "role",          role, | ||
|  |                          "title",         title, | ||
|  | 
 | ||
|  |                          "pika",          pika, | ||
|  |                          "callback",      callback, | ||
|  |                          "callback-data", callback_data, | ||
|  |                          NULL); | ||
|  |   gtk_window_set_modal (GTK_WINDOW (widget), FALSE); | ||
|  | 
 | ||
|  | 
 | ||
|  |   return widget; | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * pika_search_popup_add_result: | ||
|  |  * @popup:   the #PikaSearchPopup. | ||
|  |  * @action:  a #PikaAction to add in results list. | ||
|  |  * @section: the section to add @action. | ||
|  |  * | ||
|  |  * Adds @action in the @popup's results at @section. | ||
|  |  * The section only indicates relative order. If you want some items | ||
|  |  * to appear before other, simply use lower @section. | ||
|  |  */ | ||
|  | void | ||
|  | pika_search_popup_add_result (PikaSearchPopup *popup, | ||
|  |                               PikaAction      *action, | ||
|  |                               gint             section) | ||
|  | { | ||
|  |   gchar        **accels; | ||
|  |   GtkTreeIter    iter; | ||
|  |   GtkTreeIter    next_section; | ||
|  |   GtkListStore  *store; | ||
|  |   GtkTreeModel  *model; | ||
|  |   gchar         *markup; | ||
|  |   gchar         *action_name; | ||
|  |   gchar         *label; | ||
|  |   gchar         *escaped_label    = NULL; | ||
|  |   const gchar   *icon_name; | ||
|  |   gchar         *shortcut_helper  = NULL; | ||
|  |   const gchar   *menu_path; | ||
|  |   gchar         *menu_path_helper = NULL; | ||
|  |   const gchar   *tooltip; | ||
|  |   gchar         *escaped_tooltip  = NULL; | ||
|  |   gboolean       has_tooltip      = FALSE; | ||
|  |   gboolean       sensitive        = FALSE; | ||
|  |   const gchar   *sensitive_reason = NULL; | ||
|  |   gchar         *escaped_reason   = NULL; | ||
|  | 
 | ||
|  |   label = g_strstrip (pika_strip_uline (pika_action_get_label (action))); | ||
|  | 
 | ||
|  |   if (! label || strlen (label) == 0) | ||
|  |     { | ||
|  |       g_free (label); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |   escaped_label = g_markup_escape_text (label, -1); | ||
|  | 
 | ||
|  |   if (PIKA_IS_TOGGLE_ACTION (action)) | ||
|  |     { | ||
|  |       if (pika_toggle_action_get_active (PIKA_TOGGLE_ACTION (action))) | ||
|  |         icon_name = "gtk-ok"; | ||
|  |       else | ||
|  |         icon_name = "gtk-no"; | ||
|  |     } | ||
|  |   else | ||
|  |     { | ||
|  |       icon_name = pika_action_get_icon_name (action); | ||
|  |     } | ||
|  | 
 | ||
|  |   accels = pika_action_get_display_accels (action); | ||
|  |   if (accels && accels[0]) | ||
|  |     { | ||
|  |       gchar *formatted_label; | ||
|  |       gchar *formatted_value; | ||
|  | 
 | ||
|  |       /* TRANSLATORS: a helper label indicating the shortcut for an action,
 | ||
|  |        * it will be used within the generic "%s: %s" (also localized) string. | ||
|  |        * e.g.: "shortcut: Ctrl+Alt+O" | ||
|  |        */ | ||
|  |       formatted_label = g_markup_printf_escaped ("<u>%s</u>", _("shortcut")); | ||
|  |       formatted_value = g_markup_escape_text (accels[0], -1); | ||
|  | 
 | ||
|  |       /* TRANSLATORS: generic "title: value" label which will be used in various ways. */ | ||
|  |       shortcut_helper = g_strdup_printf (_("%s: %s"), formatted_label, formatted_value); | ||
|  | 
 | ||
|  |       g_free (formatted_label); | ||
|  |       g_free (formatted_value); | ||
|  |     } | ||
|  |   g_strfreev (accels); | ||
|  | 
 | ||
|  |   if ((menu_path = pika_action_get_menu_path (action)) != NULL) | ||
|  |     { | ||
|  |       if (strlen (menu_path) > 0) | ||
|  |         { | ||
|  |           gchar *formatted_label; | ||
|  |           gchar *formatted_value; | ||
|  | 
 | ||
|  |           /* TRANSLATORS: a helper label indicating the menu path for an action,
 | ||
|  |            * it will be used within the generic "%s: %s" (also localized) string. | ||
|  |            * e.g.: "Menu: Filters > Generic" | ||
|  |            */ | ||
|  |           formatted_label = g_markup_printf_escaped ("<u>%s</u>", _("menu")); | ||
|  |           formatted_value = g_markup_escape_text (menu_path, -1); | ||
|  | 
 | ||
|  |           /* TRANSLATORS: generic "title: value" label which will be used in various ways. */ | ||
|  |           menu_path_helper = g_strdup_printf (_("%s: %s"), formatted_label, formatted_value); | ||
|  | 
 | ||
|  |           g_free (formatted_label); | ||
|  |           g_free (formatted_value); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |   tooltip = pika_action_get_tooltip (action); | ||
|  |   if (tooltip != NULL) | ||
|  |     { | ||
|  |       escaped_tooltip = g_markup_escape_text (tooltip, -1); | ||
|  |       has_tooltip = TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |   sensitive = pika_action_is_sensitive (action, &sensitive_reason); | ||
|  |   if (sensitive_reason != NULL) | ||
|  |     { | ||
|  |       escaped_reason = g_markup_escape_text (sensitive_reason, -1); | ||
|  |     } | ||
|  | 
 | ||
|  |   markup = g_strdup_printf ("%s"                                           /* Label           */ | ||
|  |                             "<small>%s%s"                                  /* Shortcut        */ | ||
|  |                             "%s%s"                                         /* Menu path       */ | ||
|  |                             "%s<span weight='light'>%s</span>"             /* Tooltip         */ | ||
|  |                             "%s<i><span weight='ultralight'>%s</span></i>" /* Inactive reason */ | ||
|  |                             "</small>", | ||
|  |                             escaped_label, | ||
|  | 
 | ||
|  |                             shortcut_helper ? " | " : "", | ||
|  |                             shortcut_helper ? shortcut_helper : "", | ||
|  | 
 | ||
|  |                             menu_path_helper ? " | " : "", | ||
|  |                             menu_path_helper ? menu_path_helper : "", | ||
|  | 
 | ||
|  |                             has_tooltip ? "\n" : "", | ||
|  |                             has_tooltip ? escaped_tooltip : "", | ||
|  |                             escaped_reason ? "\n" : "", | ||
|  |                             escaped_reason ? escaped_reason : ""); | ||
|  | 
 | ||
|  |   action_name = g_markup_escape_text (pika_action_get_name (action), -1); | ||
|  | 
 | ||
|  |   model = gtk_tree_view_get_model (GTK_TREE_VIEW (popup->priv->results_list)); | ||
|  |   store = GTK_LIST_STORE (model); | ||
|  |   if (gtk_tree_model_get_iter_first (model, &next_section)) | ||
|  |     { | ||
|  |       while (TRUE) | ||
|  |         { | ||
|  |           gint iter_section; | ||
|  | 
 | ||
|  |           gtk_tree_model_get (model, &next_section, | ||
|  |                               COLUMN_SECTION, &iter_section, -1); | ||
|  |           if (iter_section > section) | ||
|  |             { | ||
|  |               gtk_list_store_insert_before (store, &iter, &next_section); | ||
|  |               break; | ||
|  |             } | ||
|  |           else if (! gtk_tree_model_iter_next (model, &next_section)) | ||
|  |             { | ||
|  |               gtk_list_store_append (store, &iter); | ||
|  |               break; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |   else | ||
|  |     { | ||
|  |       gtk_list_store_append (store, &iter); | ||
|  |     } | ||
|  | 
 | ||
|  |   gtk_list_store_set (store, &iter, | ||
|  |                       COLUMN_ICON,      icon_name, | ||
|  |                       COLUMN_MARKUP,    markup, | ||
|  |                       COLUMN_TOOLTIP,   action_name, | ||
|  |                       COLUMN_ACTION,    action, | ||
|  |                       COLUMN_SECTION,   section, | ||
|  |                       COLUMN_SENSITIVE, sensitive, | ||
|  |                       -1); | ||
|  | 
 | ||
|  |   g_free (markup); | ||
|  |   g_free (action_name); | ||
|  |   g_free (label); | ||
|  |   g_free (escaped_label); | ||
|  |   g_free (escaped_tooltip); | ||
|  |   g_free (escaped_reason); | ||
|  |   g_free (menu_path_helper); | ||
|  |   g_free (shortcut_helper); | ||
|  | } | ||
|  | 
 | ||
|  | /************ Private Functions ****************/ | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_constructed (GObject *object) | ||
|  | { | ||
|  |   PikaSearchPopup *popup = PIKA_SEARCH_POPUP (object); | ||
|  |   GtkWidget       *main_vbox; | ||
|  | 
 | ||
|  |   G_OBJECT_CLASS (parent_class)->constructed (object); | ||
|  | 
 | ||
|  |   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); | ||
|  |   gtk_container_add (GTK_CONTAINER (popup), main_vbox); | ||
|  |   gtk_widget_show (main_vbox); | ||
|  | 
 | ||
|  |   popup->priv->keyword_entry = gtk_entry_new (); | ||
|  |   gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popup->priv->keyword_entry), | ||
|  |                                      GTK_ENTRY_ICON_PRIMARY, "edit-find"); | ||
|  |   gtk_entry_set_icon_activatable (GTK_ENTRY (popup->priv->keyword_entry), | ||
|  |                                   GTK_ENTRY_ICON_PRIMARY, FALSE); | ||
|  |   gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popup->priv->keyword_entry), | ||
|  |                                      GTK_ENTRY_ICON_SECONDARY, PIKA_ICON_HELP); | ||
|  |   gtk_entry_set_icon_activatable (GTK_ENTRY (popup->priv->keyword_entry), | ||
|  |                                   GTK_ENTRY_ICON_SECONDARY, TRUE); | ||
|  |   gtk_box_pack_start (GTK_BOX (main_vbox), | ||
|  |                       popup->priv->keyword_entry, | ||
|  |                       FALSE, FALSE, 0); | ||
|  |   gtk_widget_show (popup->priv->keyword_entry); | ||
|  | 
 | ||
|  |   pika_search_popup_setup_results (&popup->priv->results_list, | ||
|  |                                    &popup->priv->list_view); | ||
|  |   gtk_box_pack_start (GTK_BOX (main_vbox), | ||
|  |                       popup->priv->list_view, TRUE, TRUE, 0); | ||
|  | 
 | ||
|  |   gtk_widget_set_events (GTK_WIDGET (object), | ||
|  |                          GDK_KEY_RELEASE_MASK  | | ||
|  |                          GDK_KEY_PRESS_MASK    | | ||
|  |                          GDK_BUTTON_PRESS_MASK | | ||
|  |                          GDK_SCROLL_MASK       | | ||
|  |                          GDK_SMOOTH_SCROLL_MASK); | ||
|  | 
 | ||
|  |   g_signal_connect (popup->priv->keyword_entry, "icon-press", | ||
|  |                     G_CALLBACK (keyword_entry_icon_press), | ||
|  |                     popup); | ||
|  |   g_signal_connect (popup->priv->keyword_entry, "key-press-event", | ||
|  |                     G_CALLBACK (keyword_entry_key_press_event), | ||
|  |                     popup); | ||
|  |   g_signal_connect (popup->priv->keyword_entry, "key-release-event", | ||
|  |                     G_CALLBACK (keyword_entry_key_release_event), | ||
|  |                     popup); | ||
|  | 
 | ||
|  |   g_signal_connect (popup->priv->results_list, "key-press-event", | ||
|  |                     G_CALLBACK (results_list_key_press_event), | ||
|  |                     popup); | ||
|  |   g_signal_connect (popup->priv->results_list, "row-activated", | ||
|  |                     G_CALLBACK (results_list_row_activated), | ||
|  |                     popup); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_set_property (GObject      *object, | ||
|  |                                 guint         property_id, | ||
|  |                                 const GValue *value, | ||
|  |                                 GParamSpec   *pspec) | ||
|  | { | ||
|  |   PikaSearchPopup *search_popup = PIKA_SEARCH_POPUP (object); | ||
|  | 
 | ||
|  |   switch (property_id) | ||
|  |     { | ||
|  |     case PROP_PIKA: | ||
|  |       search_popup->priv->pika = g_value_get_object (value); | ||
|  |       break; | ||
|  |     case PROP_CALLBACK: | ||
|  |       search_popup->priv->build_results = g_value_get_pointer (value); | ||
|  |       break; | ||
|  |     case PROP_CALLBACK_DATA: | ||
|  |       search_popup->priv->build_results_data = g_value_get_pointer (value); | ||
|  |       break; | ||
|  | 
 | ||
|  |     default: | ||
|  |       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); | ||
|  |       break; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_get_property (GObject      *object, | ||
|  |                                 guint         property_id, | ||
|  |                                 GValue       *value, | ||
|  |                                 GParamSpec   *pspec) | ||
|  | { | ||
|  |   PikaSearchPopup *search_popup = PIKA_SEARCH_POPUP (object); | ||
|  | 
 | ||
|  |   switch (property_id) | ||
|  |     { | ||
|  |     case PROP_PIKA: | ||
|  |       g_value_set_object (value, search_popup->priv->pika); | ||
|  |       break; | ||
|  |     case PROP_CALLBACK: | ||
|  |       g_value_set_pointer (value, search_popup->priv->build_results); | ||
|  |       break; | ||
|  |     case PROP_CALLBACK_DATA: | ||
|  |       g_value_set_pointer (value, search_popup->priv->build_results_data); | ||
|  |       break; | ||
|  | 
 | ||
|  |     default: | ||
|  |       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); | ||
|  |       break; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_size_allocate (GtkWidget     *widget, | ||
|  |                                  GtkAllocation *allocation) | ||
|  | { | ||
|  |   PikaSearchPopup *popup = PIKA_SEARCH_POPUP (widget); | ||
|  |   GdkRectangle     workarea; | ||
|  | 
 | ||
|  |   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); | ||
|  | 
 | ||
|  |   gdk_monitor_get_workarea (pika_widget_get_monitor (widget), | ||
|  |                             &workarea); | ||
|  | 
 | ||
|  |   if (window_height == 0) | ||
|  |     { | ||
|  |       /* Default to half the monitor */ | ||
|  |       window_height = workarea.height / 2; | ||
|  |     } | ||
|  | 
 | ||
|  |   if (gtk_widget_get_visible (widget) && | ||
|  |       gtk_widget_get_visible (popup->priv->list_view)) | ||
|  |     { | ||
|  |       /* Save the window height when results are shown so that resizes
 | ||
|  |        * by the user are saved across searches. | ||
|  |        */ | ||
|  |       window_height = MAX (workarea.height / 4, allocation->height); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_confirm (PikaPopup *popup) | ||
|  | { | ||
|  |   PikaSearchPopup *search_popup = PIKA_SEARCH_POPUP (popup); | ||
|  | 
 | ||
|  |   pika_search_popup_run_selected (search_popup); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | keyword_entry_icon_press (GtkEntry             *entry, | ||
|  |                           GtkEntryIconPosition  icon_pos, | ||
|  |                           GdkEvent             *event, | ||
|  |                           PikaSearchPopup      *popup) | ||
|  | { | ||
|  |   if (icon_pos == GTK_ENTRY_ICON_SECONDARY) | ||
|  |     pika_search_popup_help (popup); | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | keyword_entry_key_press_event (GtkWidget       *widget, | ||
|  |                                GdkEventKey     *event, | ||
|  |                                PikaSearchPopup *popup) | ||
|  | { | ||
|  |   gboolean event_processed = FALSE; | ||
|  | 
 | ||
|  |   if (event->keyval == GDK_KEY_Down && | ||
|  |       gtk_widget_get_visible (popup->priv->list_view)) | ||
|  |     { | ||
|  |       GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list); | ||
|  |       GtkTreePath *path; | ||
|  | 
 | ||
|  |       /* When hitting the down key while editing, select directly the
 | ||
|  |        * second item, since the first could have run directly with | ||
|  |        * Enter. */ | ||
|  |       path = gtk_tree_path_new_from_string ("1"); | ||
|  |       gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), path); | ||
|  |       gtk_tree_path_free (path); | ||
|  | 
 | ||
|  |       gtk_widget_grab_focus (GTK_WIDGET (popup->priv->results_list)); | ||
|  |       event_processed = TRUE; | ||
|  |     } | ||
|  | 
 | ||
|  |   return event_processed; | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | keyword_entry_key_release_event (GtkWidget       *widget, | ||
|  |                                  GdkEventKey     *event, | ||
|  |                                  PikaSearchPopup *popup) | ||
|  | { | ||
|  |   GtkTreeView *tree_view = GTK_TREE_VIEW (popup->priv->results_list); | ||
|  |   GtkTreePath *path; | ||
|  |   gchar       *entry_text; | ||
|  |   gint         width; | ||
|  | 
 | ||
|  |   /* These keys are already managed by key bindings. */ | ||
|  |   if (event->keyval == GDK_KEY_Escape   || | ||
|  |       event->keyval == GDK_KEY_Return   || | ||
|  |       event->keyval == GDK_KEY_KP_Enter || | ||
|  |       event->keyval == GDK_KEY_ISO_Enter) | ||
|  |     { | ||
|  |       return FALSE; | ||
|  |     } | ||
|  | 
 | ||
|  |   gtk_window_get_size (GTK_WINDOW (popup), &width, NULL); | ||
|  |   entry_text = g_strstrip (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1)); | ||
|  | 
 | ||
|  |   if (event->keyval == GDK_KEY_F1) | ||
|  |     { | ||
|  |       pika_search_popup_help (popup); | ||
|  |     } | ||
|  |   else if (strcmp (entry_text, "") != 0) | ||
|  |     { | ||
|  |       path = gtk_tree_path_new_from_string ("0"); | ||
|  |       gtk_window_resize (GTK_WINDOW (popup), | ||
|  |                          width, window_height); | ||
|  |       gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view))); | ||
|  |       gtk_widget_show_all (popup->priv->list_view); | ||
|  |       popup->priv->build_results (popup, entry_text, | ||
|  |                                   popup->priv->build_results_data); | ||
|  |       gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), path); | ||
|  |       gtk_tree_path_free (path); | ||
|  |     } | ||
|  |   else if (strcmp (entry_text, "") == 0 && (event->keyval == GDK_KEY_Down)) | ||
|  |     { | ||
|  |       path = gtk_tree_path_new_from_string ("0"); | ||
|  |       gtk_window_resize (GTK_WINDOW (popup), | ||
|  |                          width, window_height); | ||
|  |       gtk_list_store_clear (GTK_LIST_STORE (gtk_tree_view_get_model (tree_view))); | ||
|  |       gtk_widget_show_all (popup->priv->list_view); | ||
|  |       popup->priv->build_results (popup, NULL, | ||
|  |                                   popup->priv->build_results_data); | ||
|  |       gtk_tree_selection_select_path (gtk_tree_view_get_selection (tree_view), path); | ||
|  |       gtk_tree_path_free (path); | ||
|  |     } | ||
|  |   else | ||
|  |     { | ||
|  |       GtkTreeSelection *selection; | ||
|  |       GtkTreeModel     *model; | ||
|  |       GtkTreeIter       iter; | ||
|  | 
 | ||
|  |       selection = gtk_tree_view_get_selection (tree_view); | ||
|  |       gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); | ||
|  | 
 | ||
|  |       if (gtk_tree_selection_get_selected (selection, &model, &iter)) | ||
|  |         { | ||
|  |           path = gtk_tree_model_get_path (model, &iter); | ||
|  |           gtk_tree_selection_unselect_path (selection, path); | ||
|  | 
 | ||
|  |           gtk_tree_path_free (path); | ||
|  |         } | ||
|  | 
 | ||
|  |       gtk_widget_hide (popup->priv->list_view); | ||
|  |       gtk_window_resize (GTK_WINDOW (popup), width, 1); | ||
|  |     } | ||
|  | 
 | ||
|  |   g_free (entry_text); | ||
|  | 
 | ||
|  |   return TRUE; | ||
|  | } | ||
|  | 
 | ||
|  | static gboolean | ||
|  | results_list_key_press_event (GtkWidget       *widget, | ||
|  |                               GdkEventKey     *kevent, | ||
|  |                               PikaSearchPopup *popup) | ||
|  | { | ||
|  |   /* These keys are already managed by key bindings. */ | ||
|  |   g_return_val_if_fail (kevent->keyval != GDK_KEY_Escape   && | ||
|  |                         kevent->keyval != GDK_KEY_Return   && | ||
|  |                         kevent->keyval != GDK_KEY_KP_Enter && | ||
|  |                         kevent->keyval != GDK_KEY_ISO_Enter, | ||
|  |                         FALSE); | ||
|  | 
 | ||
|  |   switch (kevent->keyval) | ||
|  |     { | ||
|  |     case GDK_KEY_Up: | ||
|  |       { | ||
|  |         gboolean          event_processed = FALSE; | ||
|  |         GtkTreeSelection *selection; | ||
|  |         GtkTreeModel     *model; | ||
|  |         GtkTreeIter       iter; | ||
|  | 
 | ||
|  |         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list)); | ||
|  |         gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); | ||
|  | 
 | ||
|  |         if (gtk_tree_selection_get_selected (selection, &model, &iter)) | ||
|  |           { | ||
|  |             GtkTreePath *path = gtk_tree_model_get_path (model, &iter); | ||
|  |             gchar       *path_str; | ||
|  | 
 | ||
|  |             path_str = gtk_tree_path_to_string (path); | ||
|  |             if (strcmp (path_str, "0") == 0) | ||
|  |               { | ||
|  |                 gint start_pos; | ||
|  |                 gint end_pos; | ||
|  | 
 | ||
|  |                 gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry), | ||
|  |                                                    &start_pos, &end_pos); | ||
|  |                 gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry))); | ||
|  |                 gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry), | ||
|  |                                             start_pos, end_pos); | ||
|  | 
 | ||
|  |                 event_processed = TRUE; | ||
|  |               } | ||
|  | 
 | ||
|  |             g_free (path_str); | ||
|  |             gtk_tree_path_free (path); | ||
|  |           } | ||
|  | 
 | ||
|  |         return event_processed; | ||
|  |       } | ||
|  |     case GDK_KEY_Down: | ||
|  |       { | ||
|  |         return FALSE; | ||
|  |       } | ||
|  |     default: | ||
|  |       { | ||
|  |         gint start_pos; | ||
|  |         gint end_pos; | ||
|  | 
 | ||
|  |         gtk_editable_get_selection_bounds (GTK_EDITABLE (popup->priv->keyword_entry), | ||
|  |                                            &start_pos, &end_pos); | ||
|  |         gtk_widget_grab_focus ((GTK_WIDGET (popup->priv->keyword_entry))); | ||
|  |         gtk_editable_select_region (GTK_EDITABLE (popup->priv->keyword_entry), | ||
|  |                                     start_pos, end_pos); | ||
|  |         gtk_widget_event (GTK_WIDGET (popup->priv->keyword_entry), | ||
|  |                           (GdkEvent *) kevent); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |   return FALSE; | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | results_list_row_activated (GtkTreeView       *treeview, | ||
|  |                             GtkTreePath       *path, | ||
|  |                             GtkTreeViewColumn *col, | ||
|  |                             PikaSearchPopup   *popup) | ||
|  | { | ||
|  |   pika_search_popup_run_selected (popup); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_run_selected (PikaSearchPopup *popup) | ||
|  | { | ||
|  |   GtkTreeSelection *selection; | ||
|  |   GtkTreeModel     *model; | ||
|  |   GtkTreeIter       iter; | ||
|  | 
 | ||
|  |   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (popup->priv->results_list)); | ||
|  |   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); | ||
|  | 
 | ||
|  |   if (gtk_tree_selection_get_selected (selection, &model, &iter)) | ||
|  |     { | ||
|  |       PikaAction *action; | ||
|  | 
 | ||
|  |       gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1); | ||
|  | 
 | ||
|  |       if (pika_action_is_sensitive (action, NULL)) | ||
|  |         { | ||
|  |           /* Close the search popup on activation. */ | ||
|  |           PIKA_POPUP_CLASS (parent_class)->cancel (PIKA_POPUP (popup)); | ||
|  | 
 | ||
|  |           pika_action_activate (action); | ||
|  |         } | ||
|  | 
 | ||
|  |       g_object_unref (action); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_setup_results (GtkWidget **results_list, | ||
|  |                                  GtkWidget **list_view) | ||
|  | { | ||
|  |   gint                wid1 = 100; | ||
|  |   GtkListStore       *store; | ||
|  |   GtkCellRenderer    *cell; | ||
|  |   GtkTreeViewColumn  *column; | ||
|  | 
 | ||
|  |   *list_view = gtk_scrolled_window_new (NULL, NULL); | ||
|  |   store = gtk_list_store_new (N_COL, | ||
|  |                               G_TYPE_STRING, | ||
|  |                               G_TYPE_STRING, | ||
|  |                               G_TYPE_STRING, | ||
|  |                               PIKA_TYPE_ACTION, | ||
|  |                               G_TYPE_BOOLEAN, | ||
|  |                               G_TYPE_INT); | ||
|  |   *results_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); | ||
|  |   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (*results_list), FALSE); | ||
|  | #ifdef PIKA_UNSTABLE
 | ||
|  |   gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (*results_list), | ||
|  |                                     COLUMN_TOOLTIP); | ||
|  | #endif
 | ||
|  | 
 | ||
|  |   cell = gtk_cell_renderer_pixbuf_new (); | ||
|  |   column = gtk_tree_view_column_new_with_attributes (NULL, cell, | ||
|  |                                                      "icon-name", COLUMN_ICON, | ||
|  |                                                      "sensitive", COLUMN_SENSITIVE, | ||
|  |                                                      NULL); | ||
|  |   gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column); | ||
|  |   gtk_tree_view_column_set_min_width (column, 22); | ||
|  | 
 | ||
|  |   cell = gtk_cell_renderer_text_new (); | ||
|  |   column = gtk_tree_view_column_new_with_attributes (NULL, cell, | ||
|  |                                                      "markup",    COLUMN_MARKUP, | ||
|  |                                                      "sensitive", COLUMN_SENSITIVE, | ||
|  |                                                      NULL); | ||
|  |   gtk_tree_view_append_column (GTK_TREE_VIEW (*results_list), column); | ||
|  |   gtk_tree_view_column_set_max_width (column, wid1); | ||
|  | 
 | ||
|  |   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*list_view), | ||
|  |                                   GTK_POLICY_NEVER, | ||
|  |                                   GTK_POLICY_AUTOMATIC); | ||
|  | 
 | ||
|  |   gtk_container_add (GTK_CONTAINER (*list_view), *results_list); | ||
|  |   g_object_unref (G_OBJECT (store)); | ||
|  | } | ||
|  | 
 | ||
|  | static void | ||
|  | pika_search_popup_help (PikaSearchPopup *popup) | ||
|  | { | ||
|  |   GtkTreeView      *tree_view; | ||
|  |   const gchar      *help_id = NULL; | ||
|  |   PikaAction       *action  = NULL; | ||
|  |   GtkTreeSelection *selection; | ||
|  |   GtkTreeModel     *model; | ||
|  |   GtkTreeIter       iter; | ||
|  | 
 | ||
|  |   tree_view = GTK_TREE_VIEW (popup->priv->results_list); | ||
|  |   selection = gtk_tree_view_get_selection (tree_view); | ||
|  | 
 | ||
|  |   if (gtk_tree_selection_get_selected (selection, &model, &iter)) | ||
|  |     { | ||
|  |       gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1); | ||
|  |       help_id = pika_action_get_help_id (action); | ||
|  |     } | ||
|  | 
 | ||
|  |   if (help_id == NULL) | ||
|  |     help_id = PIKA_HELP_ACTION_SEARCH_DIALOG; | ||
|  | 
 | ||
|  |   pika_help (popup->priv->pika, NULL, NULL, help_id); | ||
|  |   g_clear_object (&action); | ||
|  | } |