/** * @file gnome-cmd-quicksearch-popup.cc * @copyright (C) 2001-2006 Marcus Bjurman\n * @copyright (C) 2007-2012 Piotr Eljasiak\n * @copyright (C) 2013-2023 Uwe Scholz\n * * @copyright 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 2 of the License, or * (at your option) any later version. * * @copyright 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. * * @copyright You should have received a copy of the GNU 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. */ #include #include "gnome-cmd-includes.h" #include "gnome-cmd-quicksearch-popup.h" #include "gnome-cmd-file-list.h" #include "gnome-cmd-file.h" #include "gnome-cmd-data.h" #include "gnome-cmd-main-win.h" using namespace std; static GtkWindowClass *parent_class = nullptr; struct GnomeCmdQuicksearchPopupPrivate { GnomeCmdFileList *fl; GList *matches; GList *pos; GnomeCmdFile *last_focused_file; }; inline void focus_file (GnomeCmdQuicksearchPopup *popup, GnomeCmdFile *f) { if (f->is_dotdot) return; popup->priv->last_focused_file = f; gint row = popup->priv->fl->get_row_from_file(f); gtk_clist_moveto (GTK_CLIST (popup->priv->fl), row, 0, 1, 0); gtk_clist_freeze (GTK_CLIST (popup->priv->fl)); GNOME_CMD_CLIST (popup->priv->fl)->drag_motion_row = row; gtk_clist_thaw (GTK_CLIST (popup->priv->fl)); } static void set_filter (GnomeCmdQuicksearchPopup *popup, const gchar *text) { g_return_if_fail (GNOME_CMD_IS_FILE_LIST (popup->priv->fl)); g_return_if_fail (text != nullptr); gboolean first = TRUE; gtk_clist_freeze (GTK_CLIST (popup->priv->fl)); GNOME_CMD_CLIST (popup->priv->fl)->drag_motion_row = -1; gtk_clist_thaw (GTK_CLIST (popup->priv->fl)); if (popup->priv->matches) { g_list_free (popup->priv->matches); popup->priv->matches = nullptr; } gchar *pattern; if (gnome_cmd_data.options.quick_search_exact_match_begin) pattern = gnome_cmd_data.options.quick_search_exact_match_end ? g_strdup (text) : g_strconcat (text, "*", nullptr); else pattern = gnome_cmd_data.options.quick_search_exact_match_end ? g_strconcat ("*", text, nullptr) : g_strconcat ("*", text, "*", nullptr); for (GList *files = popup->priv->fl->get_visible_files(); files; files = files->next) { auto f = static_cast (files->data); if (gnome_cmd_filter_fnmatch (pattern, g_file_info_get_display_name(f->gFileInfo), gnome_cmd_data.options.case_sens_sort)) { if (first) { focus_file (popup, f); first = FALSE; } popup->priv->matches = g_list_append (popup->priv->matches, f); } } g_free (pattern); // If no file matches the new filter, focus on the last file that matched a previous filter if (popup->priv->matches == nullptr && popup->priv->last_focused_file != nullptr) popup->priv->matches = g_list_append (popup->priv->matches, popup->priv->last_focused_file); popup->priv->pos = popup->priv->matches; } inline void hide_popup (GnomeCmdQuicksearchPopup *popup) { gtk_grab_remove (popup->entry); gtk_clist_freeze (GTK_CLIST (popup->priv->fl)); GNOME_CMD_CLIST (popup->priv->fl)->drag_motion_row = -1; gtk_clist_thaw (GTK_CLIST (popup->priv->fl)); gtk_widget_grab_focus (GTK_WIDGET (popup->priv->fl)); if (popup->priv->matches) g_list_free (popup->priv->matches); popup->priv->last_focused_file = nullptr; gtk_widget_hide (GTK_WIDGET (popup)); } static void on_text_changed (GtkEntry *entry, GnomeCmdQuicksearchPopup *popup) { set_filter (popup, gtk_entry_get_text (GTK_ENTRY (entry))); if (popup->priv->pos) focus_file (popup, GNOME_CMD_FILE (popup->priv->pos->data)); } static gboolean on_key_pressed (GtkWidget *entry, GdkEventKey *event, GnomeCmdQuicksearchPopup *popup) { if (event->type == GDK_KEY_PRESS && gtk_editable_get_editable (GTK_EDITABLE (entry))) if (gtk_entry_im_context_filter_keypress (GTK_ENTRY (entry), event)) return TRUE; // While in quicksearch, treat "ALT/CTRL + key" as a simple "key" event->state &= ~(GDK_CONTROL_MASK | GDK_MOD1_MASK); switch (event->keyval) { case GDK_Escape: popup->priv->fl->select_row(GNOME_CMD_CLIST (popup->priv->fl)->drag_motion_row); hide_popup (popup); return TRUE; // for more convenience do ENTER action directly on the current quicksearch item case GDK_Return: case GDK_KP_Enter: // for more convenience jump direct to Fx function on the current quicksearch item case GDK_F3: case GDK_F4: case GDK_F5: case GDK_F6: case GDK_F8: popup->priv->fl->select_row(GNOME_CMD_CLIST (popup->priv->fl)->drag_motion_row); hide_popup (popup); main_win->key_pressed(event); return TRUE; default: break; } return FALSE; } static gboolean on_key_pressed_after (GtkWidget *entry, GdkEventKey *event, GnomeCmdQuicksearchPopup *popup) { switch (event->keyval) { case GDK_Up: { if (popup->priv->pos) { if (popup->priv->pos->prev) popup->priv->pos = popup->priv->pos->prev; else popup->priv->pos = g_list_last (popup->priv->matches); } } break; case GDK_Down: { if (popup->priv->pos) { if (popup->priv->pos->next) popup->priv->pos = popup->priv->pos->next; else popup->priv->pos = popup->priv->matches; } } break; default: break; } if (popup->priv->pos) focus_file (popup, GNOME_CMD_FILE (popup->priv->pos->data)); return TRUE; } static void on_button_press (GtkWidget *entry, GdkEventButton *event, GnomeCmdQuicksearchPopup *popup) { gboolean ret; hide_popup (popup); gtk_signal_emit_by_name (GTK_OBJECT (popup->priv->fl), "button-press-event", event, &ret); } inline void set_popup_position (GnomeCmdQuicksearchPopup *popup) { GtkWidget *wid = GTK_WIDGET (popup->priv->fl); gint x, y, w, h; gdk_window_get_origin (gtk_widget_get_window (wid), &x, &y); gdk_drawable_get_size (gtk_widget_get_window (wid), &w, &h); y += h; gtk_widget_set_uposition (GTK_WIDGET (popup), x, y); } static GObject *constructor (GType gtype, guint n_properties, GObjectConstructParam *properties) { // change construct only properties for (guint i = 0; i < n_properties; ++i) { gchar const *name = g_param_spec_get_name (properties[i].pspec); if (!strcmp (name, "type")) { g_value_set_enum (properties[i].value, GTK_WINDOW_POPUP); break; } } return G_OBJECT_CLASS (parent_class)->constructor (gtype, n_properties, properties); } static void destroy (GtkObject *object) { GnomeCmdQuicksearchPopup *popup = GNOME_CMD_QUICKSEARCH_POPUP (object); g_free (popup->priv); if (GTK_OBJECT_CLASS (parent_class)->destroy) (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void map (GtkWidget *widget) { if (GTK_WIDGET_CLASS (parent_class)->map != nullptr) GTK_WIDGET_CLASS (parent_class)->map (widget); } static void class_init (GnomeCmdQuicksearchPopupClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); parent_class = (GtkWindowClass *) gtk_type_class (gtk_window_get_type ()); gobject_class->constructor = constructor; object_class->destroy = destroy; widget_class->map = ::map; } static void init (GnomeCmdQuicksearchPopup *popup) { popup->priv = g_new0 (GnomeCmdQuicksearchPopupPrivate, 1); popup->frame = gtk_frame_new (nullptr); gtk_frame_set_shadow_type (GTK_FRAME (popup->frame), GTK_SHADOW_OUT); popup->box = gtk_hbox_new (FALSE, 2); popup->lbl = gtk_label_new (_("Search")); popup->entry = gtk_entry_new (); g_object_ref (popup->frame); g_object_ref (popup->box); g_object_ref (popup->lbl); g_object_ref (popup->entry); gtk_box_pack_start (GTK_BOX (popup->box), popup->lbl, FALSE, TRUE, 0); gtk_box_pack_start (GTK_BOX (popup->box), popup->entry, FALSE, TRUE, 0); gtk_container_add (GTK_CONTAINER (popup->frame), popup->box); gtk_container_add (GTK_CONTAINER (popup), popup->frame); g_signal_connect (popup->entry, "key-press-event", G_CALLBACK (on_key_pressed), popup); g_signal_connect_after (popup->entry, "key-press-event", G_CALLBACK (on_key_pressed_after), popup); g_signal_connect_after (popup->entry, "changed", G_CALLBACK (on_text_changed), popup); g_signal_connect (popup->entry, "button-press-event", G_CALLBACK (on_button_press), popup); gtk_widget_show (popup->lbl); gtk_widget_show (popup->entry); gtk_widget_show (popup->box); gtk_widget_show (popup->frame); gtk_grab_add (popup->entry); } /*********************************** * Public functions ***********************************/ GtkType gnome_cmd_quicksearch_popup_get_type () { static GtkType type = 0; if (type == 0) { GtkTypeInfo info = { (gchar*) "GnomeCmdQuicksearchPopup", sizeof (GnomeCmdQuicksearchPopup), sizeof (GnomeCmdQuicksearchPopupClass), (GtkClassInitFunc) class_init, (GtkObjectInitFunc) init, /* reserved_1 */ nullptr, /* reserved_2 */ nullptr, (GtkClassInitFunc) nullptr }; type = gtk_type_unique (gtk_window_get_type (), &info); } return type; } GtkWidget *gnome_cmd_quicksearch_popup_new (GnomeCmdFileList *fl) { GnomeCmdQuicksearchPopup *popup; popup = static_cast (g_object_new (GNOME_CMD_TYPE_QUICKSEARCH_POPUP, nullptr)); popup->priv->fl = fl; popup->priv->last_focused_file = nullptr; set_popup_position (popup); return GTK_WIDGET (popup); }