From 92bd4ebbf1a6d35798a1a30aea7d7102e92b17c2 Mon Sep 17 00:00:00 2001 From: Ky-Anh Huynh Date: Sun, 5 Jan 2025 12:34:02 +0100 Subject: [PATCH] commander: small improvement on listing and panel width When working with different directories that have some common files that have long full path names, it's hard to find the right file from the list. This is because the panel's width is 500 and the full path may not be shown completely within the panel as below ``` <-------- visible to user --------> <----- invisible to users -------------------> /-----------------------------------\ | /foo/bar/directory/with/very/long-|/path/name1/sample.txt | Files -> Recent Files -> /foo/bar/|directory/with/very/long-/path1/name/sample.txt | /foo/bar/directory/with/very/long-|/path/name2/sample.txt | Files -> Recent Files -> /foo/bar/|directory/with/very/long-/path2/name/sample.txt \-----------------------------------/ ``` In this illustration, when looking up `sample.txt`, the user has no idea about the directory information (both `path/name1` and `path/name2` do matching) - [ ] The panel's width is one half of the width of the Geany window when this amount is greater than or equals to 250 - [ ] Otherwise the panel's width defaults to the width of the Geany window - [ ] For labels, don't display full names; only display the parent directory's basename and the file name The previous illustration now becomes ``` /-----------------------------------\ | name1/sample.txt | | Files -> Recent Files -> /foo/bar/|directory/with/very/long-/path/name1/sample.txt | name2/sample.txt | | Files -> Recent Files -> /foo/bar/|directory/with/very/long-/path/name2/sample.txt \-----------------------------------/ ``` and this helps user to quickly find the right files. Of course this doesn't solve all edge cases when there are a few more levels of duplication in full path names. --- commander/src/commander-plugin.c | 201 +++++++++++++++++-------------- 1 file changed, 112 insertions(+), 89 deletions(-) diff --git a/commander/src/commander-plugin.c b/commander/src/commander-plugin.c index 62b0fb7ca..de1b1f6eb 100644 --- a/commander/src/commander-plugin.c +++ b/commander/src/commander-plugin.c @@ -1,20 +1,20 @@ /* - * + * * Copyright (C) 2012 Colomban Wendling - * + * * 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 . - * + * */ #include "config.h" @@ -90,7 +90,7 @@ struct { GtkWidget *view; GtkListStore *store; GtkTreeModel *sort; - + GtkTreePath *last_path; } plugin_data = { NULL, NULL, NULL, @@ -133,7 +133,7 @@ get_score (const gchar *needle, } else if (! *needle || ! *haystack) { return *needle == 0; } - + if (IS_SEPARATOR (*haystack)) { return get_score (needle + IS_SEPARATOR (*needle), haystack + 1); } @@ -145,7 +145,7 @@ get_score (const gchar *needle, if (*needle == *haystack) { gint a = get_score (needle + 1, haystack + 1) + 1 + IS_SEPARATOR (haystack[1]); gint b = get_score (needle, next_separator (haystack)); - + return MAX (a, b); } else { return get_score (needle, next_separator (haystack)); @@ -174,12 +174,12 @@ key_score (const gchar *key_, gchar *text = g_utf8_casefold (text_, -1); gchar *key = g_utf8_casefold (key_, -1); gint score; - + score = get_score (key, text) + get_score (key, path_basename (text)) / 2; - + g_free (text); g_free (key); - + return score; } @@ -188,7 +188,7 @@ get_key (gint *type_) { gint type = COL_TYPE_ANY; const gchar *key = gtk_entry_get_text (GTK_ENTRY (plugin_data.entry)); - + if (g_str_has_prefix (key, "f:")) { key += 2; type = COL_TYPE_FILE; @@ -196,11 +196,11 @@ get_key (gint *type_) key += 2; type = COL_TYPE_MENU_ITEM; } - + if (type_) { *type_ = type; } - + return key; } @@ -209,7 +209,7 @@ tree_view_set_cursor_from_iter (GtkTreeView *view, GtkTreeIter *iter) { GtkTreePath *path; - + path = gtk_tree_model_get_path (gtk_tree_view_get_model (view), iter); gtk_tree_view_set_cursor (view, path, NULL, FALSE); gtk_tree_path_free (path); @@ -224,7 +224,7 @@ tree_view_move_focus (GtkTreeView *view, GtkTreePath *path; GtkTreeModel *model = gtk_tree_view_get_model (view); gboolean valid = FALSE; - + gtk_tree_view_get_cursor (view, &path, NULL); if (! path) { valid = gtk_tree_model_get_iter_first (model, &iter); @@ -234,14 +234,14 @@ tree_view_move_focus (GtkTreeView *view, valid = gtk_tree_model_get_iter_first (model, &iter); if (valid && amount > 0) { GtkTreeIter prev; - + do { prev = iter; } while (gtk_tree_model_iter_next (model, &iter)); iter = prev; } break; - + case GTK_MOVEMENT_PAGES: /* FIXME: move by page */ case GTK_MOVEMENT_DISPLAY_LINES: @@ -253,19 +253,19 @@ tree_view_move_focus (GtkTreeView *view, } else if (amount < 0) { while ((valid = gtk_tree_path_prev (path)) && --amount > 0) ; - + if (valid) { gtk_tree_model_get_iter (model, &iter, path); } } break; - + default: g_assert_not_reached (); } gtk_tree_path_free (path); } - + if (valid) { tree_view_set_cursor_from_iter (view, &iter); } else { @@ -278,7 +278,7 @@ tree_view_activate_focused_row (GtkTreeView *view) { GtkTreePath *path; GtkTreeViewColumn *column; - + gtk_tree_view_get_cursor (view, &path, &column); if (path) { gtk_tree_view_row_activated (view, path, column); @@ -293,7 +293,7 @@ store_populate_menu_items (GtkListStore *store, { GList *children; GList *node; - + children = gtk_container_get_children (GTK_CONTAINER (menu)); for (node = children; node; node = node->next) { if (GTK_IS_SEPARATOR_MENU_ITEM (node->data) || @@ -305,7 +305,7 @@ store_populate_menu_items (GtkListStore *store, gchar *item_label; gboolean use_underline; GtkStockItem item; - + /* GtkStock is deprectaed since GTK 3.10, but we have to use it in order * to get the actual label of the menu item */ G_GNUC_BEGIN_IGNORE_DEPRECATIONS @@ -319,25 +319,25 @@ store_populate_menu_items (GtkListStore *store, use_underline = gtk_menu_item_get_use_underline (node->data); } G_GNUC_END_IGNORE_DEPRECATIONS - + /* remove underlines */ if (use_underline) { gchar *p = item_label; gsize len = strlen (p); - + while ((p = strchr (p, '_')) != NULL) { len -= (gsize) (p - item_label); - + memmove (p, p + 1, len); } } - + if (parent_path) { path = g_strconcat (parent_path, PATH_SEPARATOR, item_label, NULL); } else { path = g_strdup (item_label); } - + submenu = gtk_menu_item_get_submenu (node->data); if (submenu) { /* go deeper in the menus... */ @@ -345,28 +345,39 @@ store_populate_menu_items (GtkListStore *store, } else { gchar *tmp; gchar *tooltip; - gchar *label = g_markup_printf_escaped ("%s", item_label); - + gchar *basename = g_path_get_basename (item_label); + gchar *dirname = g_path_get_dirname (item_label); + gchar *dirname_basename = g_path_get_basename(dirname); + gchar *label; + if (g_strcmp0(".", dirname_basename) == 0) { + label = g_markup_printf_escaped ("%s", basename); + } else { + label = g_markup_printf_escaped ("%s/%s", dirname_basename, basename); + } + tooltip = gtk_widget_get_tooltip_markup (node->data); if (tooltip) { SETPTR (label, g_strconcat (label, "\n", tooltip, "", NULL)); g_free (tooltip); } - + tmp = g_markup_escape_text (path, -1); SETPTR (label, g_strconcat (label, "\n", tmp, "", NULL)); g_free (tmp); - + gtk_list_store_insert_with_values (store, NULL, -1, COL_LABEL, label, COL_PATH, path, COL_TYPE, COL_TYPE_MENU_ITEM, COL_WIDGET, node->data, -1); - + g_free (label); + g_free (basename); + g_free (dirname); + g_free (dirname_basename); } - + g_free (item_label); g_free (path); } else { @@ -383,7 +394,7 @@ find_menubar (GtkContainer *container) GList *children; GList *node; GtkWidget *menubar = NULL; - + children = gtk_container_get_children (container); for (node = children; ! menubar && node; node = node->next) { if (GTK_IS_MENU_BAR (node->data)) { @@ -393,7 +404,7 @@ find_menubar (GtkContainer *container) } } g_list_free (children); - + return menubar; } @@ -402,19 +413,22 @@ fill_store (GtkListStore *store) { GtkWidget *menubar; guint i = 0; - + /* menu items */ menubar = find_menubar (GTK_CONTAINER (geany_data->main_widgets->window)); store_populate_menu_items (store, GTK_MENU_SHELL (menubar), NULL); - + /* open files */ foreach_document (i) { gchar *basename = g_path_get_basename (DOC_FILENAME (documents[i])); - gchar *label = g_markup_printf_escaped ("%s\n" + gchar *dirname = g_path_get_dirname (DOC_FILENAME (documents[i])); + gchar *dirname_basename = g_path_get_basename(dirname); + gchar *label = g_markup_printf_escaped ("%s/%s\n" "%s", + dirname_basename, basename, DOC_FILENAME (documents[i])); - + gtk_list_store_insert_with_values (store, NULL, -1, COL_LABEL, label, COL_PATH, DOC_FILENAME (documents[i]), @@ -422,6 +436,8 @@ fill_store (GtkListStore *store) COL_DOCUMENT, documents[i], -1); g_free (basename); + g_free (dirname); + g_free (dirname_basename); g_free (label); } } @@ -440,23 +456,23 @@ sort_func (GtkTreeModel *model, gint typeb; gint type; const gchar *key = get_key (&type); - + gtk_tree_model_get (model, a, COL_PATH, &patha, COL_TYPE, &typea, -1); gtk_tree_model_get (model, b, COL_PATH, &pathb, COL_TYPE, &typeb, -1); - + scorea = key_score (key, patha); scoreb = key_score (key, pathb); - + if (! (typea & type)) { scorea -= 0xf000; } if (! (typeb & type)) { scoreb -= 0xf000; } - + g_free (patha); g_free (pathb); - + return scoreb - scorea; } @@ -469,24 +485,24 @@ on_panel_key_press_event (GtkWidget *widget, case GDK_KEY_Escape: gtk_widget_hide (widget); return TRUE; - + case GDK_KEY_Tab: /* avoid leaving the entry */ return TRUE; - + case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_ISO_Enter: tree_view_activate_focused_row (GTK_TREE_VIEW (plugin_data.view)); return TRUE; - + case GDK_KEY_Page_Up: case GDK_KEY_Page_Down: tree_view_move_focus (GTK_TREE_VIEW (plugin_data.view), GTK_MOVEMENT_PAGES, event->keyval == GDK_KEY_Page_Up ? -1 : 1); return TRUE; - + case GDK_KEY_Up: case GDK_KEY_Down: { tree_view_move_focus (GTK_TREE_VIEW (plugin_data.view), @@ -495,7 +511,7 @@ on_panel_key_press_event (GtkWidget *widget, return TRUE; } } - + return FALSE; } @@ -507,7 +523,7 @@ on_entry_text_notify (GObject *object, GtkTreeIter iter; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); GtkTreeModel *model = gtk_tree_view_get_model (view); - + /* we force re-sorting the whole model from how it was before, and the * back to the new filter. this is somewhat hackish but since we don't * know the original sorting order, and GtkTreeSortable don't have a @@ -515,7 +531,7 @@ on_entry_text_notify (GObject *object, gtk_tree_model_sort_reset_default_sort_func (GTK_TREE_MODEL_SORT (model)); gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), sort_func, NULL, NULL); - + if (gtk_tree_model_get_iter_first (model, &iter)) { tree_view_set_cursor_from_iter (view, &iter); } @@ -533,13 +549,13 @@ on_panel_hide (GtkWidget *widget, gpointer dummy) { GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); - + if (plugin_data.last_path) { gtk_tree_path_free (plugin_data.last_path); plugin_data.last_path = NULL; } gtk_tree_view_get_cursor (view, &plugin_data.last_path, NULL); - + gtk_list_store_clear (plugin_data.store); } @@ -549,11 +565,11 @@ on_panel_show (GtkWidget *widget, { GtkTreePath *path; GtkTreeView *view = GTK_TREE_VIEW (plugin_data.view); - + fill_store (plugin_data.store); - + gtk_widget_grab_focus (plugin_data.entry); - + if (plugin_data.last_path) { gtk_tree_view_set_cursor (view, plugin_data.last_path, NULL, FALSE); gtk_tree_view_scroll_to_cell (view, plugin_data.last_path, NULL, @@ -565,7 +581,7 @@ on_panel_show (GtkWidget *widget, gtk_tree_path_free (path); } else { GtkTreeIter iter; - + if (gtk_tree_model_get_iter_first (gtk_tree_view_get_model (view), &iter)) { tree_view_set_cursor_from_iter (GTK_TREE_VIEW (plugin_data.view), &iter); } @@ -580,31 +596,31 @@ on_view_row_activated (GtkTreeView *view, { GtkTreeModel *model = gtk_tree_view_get_model (view); GtkTreeIter iter; - + if (gtk_tree_model_get_iter (model, &iter, path)) { gint type; - + gtk_tree_model_get (model, &iter, COL_TYPE, &type, -1); - + switch (type) { case COL_TYPE_FILE: { GeanyDocument *doc; gint page; - + gtk_tree_model_get (model, &iter, COL_DOCUMENT, &doc, -1); page = document_get_notebook_page (doc); gtk_notebook_set_current_page (GTK_NOTEBOOK (geany_data->main_widgets->notebook), page); break; } - + case COL_TYPE_MENU_ITEM: { GtkMenuItem *item; - + gtk_tree_model_get (model, &iter, COL_WIDGET, &item, -1); gtk_menu_item_activate (item); g_object_unref (item); - + break; } } @@ -627,24 +643,24 @@ score_cell_data (GtkTreeViewColumn *column, gint type; gint width, old_width; const gchar *key = get_key (&type); - + gtk_tree_model_get (model, iter, COL_PATH, &path, COL_TYPE, &pathtype, -1); - + score = key_score (key, path); if (! (pathtype & type)) { score -= 0xf000; } - + text = g_strdup_printf ("%d", score); g_object_set (cell, "text", text, NULL); - + /* automatic column sizing is buggy, so just make an acceptable wild guess */ width = 8 + strlen (text) * 10; old_width = gtk_tree_view_column_get_fixed_width (col); if (old_width < width) { gtk_tree_view_column_set_fixed_width (col, width); } - + g_free (text); g_free (path); } @@ -658,10 +674,17 @@ create_panel (void) GtkWidget *scroll; GtkTreeViewColumn *col; GtkCellRenderer *cell; - + + int window_width; + int window_height; + gtk_window_get_size(GTK_WINDOW(geany_data->main_widgets->window), &window_width, &window_height); + if (window_width >= 500) { + window_width = window_width / 2; + } + plugin_data.panel = g_object_new (GTK_TYPE_WINDOW, "decorated", FALSE, - "default-width", 500, + "default-width", window_width, "default-height", 200, "transient-for", geany_data->main_widgets->window, "window-position", GTK_WIN_POS_CENTER_ON_PARENT, @@ -677,35 +700,35 @@ create_panel (void) G_CALLBACK (on_panel_hide), NULL); g_signal_connect (plugin_data.panel, "key-press-event", G_CALLBACK (on_panel_key_press_event), NULL); - + frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (plugin_data.panel), frame); - + box = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), box); - + plugin_data.entry = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (box), plugin_data.entry, FALSE, TRUE, 0); - + plugin_data.store = gtk_list_store_new (COL_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, GTK_TYPE_WIDGET, G_TYPE_POINTER); - + plugin_data.sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (plugin_data.store)); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (plugin_data.sort), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); - + scroll = g_object_new (GTK_TYPE_SCROLLED_WINDOW, "hscrollbar-policy", GTK_POLICY_AUTOMATIC, "vscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL); gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0); - + plugin_data.view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (plugin_data.sort)); gtk_widget_set_can_focus (plugin_data.view, FALSE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (plugin_data.view), FALSE); @@ -725,13 +748,13 @@ create_panel (void) g_signal_connect (plugin_data.view, "row-activated", G_CALLBACK (on_view_row_activated), NULL); gtk_container_add (GTK_CONTAINER (scroll), plugin_data.view); - + /* connect entry signals after the view is created as they use it */ g_signal_connect (plugin_data.entry, "notify::text", G_CALLBACK (on_entry_text_notify), NULL); g_signal_connect (plugin_data.entry, "activate", G_CALLBACK (on_entry_activate), NULL); - + gtk_widget_show_all (frame); } @@ -741,12 +764,12 @@ on_kb_show_panel (GeanyKeyBinding *kb, gpointer data) { const gchar *prefix = data; - + gtk_widget_show (plugin_data.panel); - + if (prefix) { const gchar *key = gtk_entry_get_text (GTK_ENTRY (plugin_data.entry)); - + if (! g_str_has_prefix (key, prefix)) { gtk_entry_set_text (GTK_ENTRY (plugin_data.entry), prefix); } @@ -754,7 +777,7 @@ on_kb_show_panel (GeanyKeyBinding *kb, gtk_editable_select_region (GTK_EDITABLE (plugin_data.entry), g_utf8_strlen (prefix, -1), -1); } - + return TRUE; } @@ -762,7 +785,7 @@ static gboolean on_plugin_idle_init (gpointer dummy) { create_panel (); - + return FALSE; } @@ -770,7 +793,7 @@ void plugin_init (GeanyData *data) { GeanyKeyGroup *group; - + group = plugin_set_key_group (geany_plugin, "commander", KB_COUNT, NULL); keybindings_set_item_full (group, KB_SHOW_PANEL, 0, 0, "show_panel", _("Show Command Panel"), NULL, @@ -783,7 +806,7 @@ plugin_init (GeanyData *data) "show_panel_files", _("Show Command Panel (Files Only)"), NULL, on_kb_show_panel, (gpointer) "f:", NULL); - + /* delay for other plugins to have a chance to load before, so we will * include their items */ plugin_idle_add (geany_plugin, on_plugin_idle_init, NULL);