1 /*
2 ** 1998-09-27 - A pretty cool dialog, that allows the selection of a command sequence
3 ** OR a built-in command. Should be very convenient.
4 ** 1998-12-16 - Now tracks and remembers the collapsed/expanded state of the two sub-trees.
5 ** 1999-03-13 - Adjustments for new dialog module.
6 ** 1999-05-08 - Slight adjustments; now keeps the command sequence as a GString.
7 ** 1999-05-09 - Celbrated my birthday by changing my old (sucky) auto-completion code into
8 ** use of glib's g_completion_XXX() API. A lot nicer, although a bit tricky
9 ** and (for me) non-intuitive to use. I think it works now, though.
10 ** 1999-06-19 - Adapted for new dialog module.
11 ** 1999-08-29 - Did trivial modifications to retain the command entered between uses.
12 */
13
14 #include "gentoo.h"
15
16 #include <gdk/gdkkeysyms.h>
17
18 #include "dialog.h"
19 #include "guiutil.h"
20 #include "strutil.h"
21
22 #include "cmdseq_dialog.h"
23
24 /* ----------------------------------------------------------------------------------------- */
25
26 typedef struct {
27 GString *cmd; /* Keep at top for correct initialization. */
28 Dialog *dlg;
29 GtkWidget *vbox;
30 GtkWidget *entry;
31 GList *builtin, *cmdseq;
32 GList *clist, *citer;
33 GtkTreeStore *store;
34 GtkListStore *lstore; /* Only used for completion, to work around sub-tree issues. */
35 gboolean expanded[2];
36 } CDlg;
37
38 static CDlg the_cdlg = { NULL };
39
40 /* ----------------------------------------------------------------------------------------- */
41
42 /* 1998-09-27 - Add <key> to a list, sorted. */
insert_key(gpointer key,gpointer data,gpointer user)43 static void insert_key(gpointer key, gpointer data, gpointer user)
44 {
45 GList **list = user;
46
47 *list = g_list_insert_sorted(*list, key, (GCompareFunc) strcmp);
48 }
49
50 /* 1998-09-27 - Take a hash table with string keys, and build a linear list from it, sorting
51 ** the keys in lexicographically.
52 */
hash_to_list(GHashTable * hash)53 static GList * hash_to_list(GHashTable *hash)
54 {
55 GList *list = NULL;
56
57 if(hash != NULL)
58 g_hash_table_foreach(hash, insert_key, &list);
59
60 return list;
61 }
62
63 /* ----------------------------------------------------------------------------------------- */
64
65 /* 2004-12-03 - Cursor moved, i.e. item was selected. */
evt_cursor_changed(GtkWidget * wid,gpointer user)66 static void evt_cursor_changed(GtkWidget *wid, gpointer user)
67 {
68 CDlg *cdlg = user;
69 GtkTreePath *path;
70 GtkTreeIter iter;
71 gchar *text = NULL;
72
73 gtk_tree_view_get_cursor(GTK_TREE_VIEW(wid), &path, NULL);
74 if(path == NULL)
75 return;
76 gtk_tree_model_get_iter(GTK_TREE_MODEL(cdlg->store), &iter, path);
77 if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(cdlg->store), &iter) != 0)
78 return;
79 gtk_tree_model_get(GTK_TREE_MODEL(cdlg->store), &iter, 0, &text, -1);
80 if(text != NULL)
81 {
82 gtk_entry_set_text(GTK_ENTRY(cdlg->entry), text);
83 gtk_widget_grab_focus(cdlg->entry);
84 g_string_assign(cdlg->cmd, text);
85 g_free(text);
86 }
87 }
88
89 /* 2004-11-21 - Expanded/collapsed state of a tree row changed; update state. */
evt_row_collapsed_expanded(GtkWidget * treeview,GtkTreeIter * iter,GtkTreePath * path,gpointer user)90 static void evt_row_collapsed_expanded(GtkWidget *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer user)
91 {
92 gint *ind = gtk_tree_path_get_indices(path);
93 CDlg *cdlg = user;
94
95 cdlg->expanded[ind[0]] = gtk_tree_view_row_expanded(GTK_TREE_VIEW(treeview), path);
96 }
97
98 /* 2009-10-22 - Accept double-clicked row, for speed. */
evt_row_activated(GtkWidget * wid,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user)99 static void evt_row_activated(GtkWidget *wid, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user)
100 {
101 CDlg *cdlg = user;
102
103 dlg_dialog_sync_close(cdlg->dlg, DLG_POSITIVE);
104 }
105
106 /* 2004-11-21 - Append a subtree from a list of strings. */
append_subtree_list(const GList * list,GtkTreeStore * store,GtkTreeIter * parent)107 static void append_subtree_list(const GList *list, GtkTreeStore *store, GtkTreeIter *parent)
108 {
109 for(; list != NULL; list = g_list_next(list))
110 {
111 GtkTreeIter iter;
112
113 gtk_tree_store_append(store, &iter, parent);
114 gtk_tree_store_set(store, &iter, 0, list->data, -1);
115 }
116 }
117
append_liststore_list(const GList * list,GtkListStore * store)118 static void append_liststore_list(const GList *list, GtkListStore *store)
119 {
120 for(; list != NULL; list = g_list_next(list))
121 {
122 GtkTreeIter iter;
123
124 gtk_list_store_insert_with_values(store, &iter, -1, 0, list->data, -1);
125 }
126 }
127
128 /* 2012-05-23 - Callback for GTK+'s built-in completion support. This replaces the old
129 ** TAB-triggered completion, since that in turn was based on GCompletion
130 ** (in glib), which has been deprecated.
131 */
cb_match_func(GtkEntryCompletion * ec,const gchar * key,GtkTreeIter * iter,gpointer user)132 static gboolean cb_match_func(GtkEntryCompletion *ec, const gchar *key, GtkTreeIter *iter, gpointer user)
133 {
134 CDlg *cdlg = user;
135 const gchar *text = gtk_entry_get_text(GTK_ENTRY(cdlg->entry));
136 gchar *cname;
137 gboolean ret;
138
139 gtk_tree_model_get(GTK_TREE_MODEL(gtk_entry_completion_get_model(ec)), iter, 0, &cname, -1);
140 ret = strstr(cname, text) != NULL;
141 g_free(cname);
142
143 return ret;
144 }
145
146 /* 2004-11-21 - Build tree holding built-in and user commands. New GTK+ 2.0 version. */
build_tree(MainInfo * min,CDlg * cdlg)147 static GtkWidget * build_tree(MainInfo *min, CDlg *cdlg)
148 {
149 GtkTreeIter iter;
150 GtkWidget *view;
151 GtkCellRenderer *cr;
152 GtkTreeViewColumn *vc;
153 GtkTreePath *path;
154 GtkEntryCompletion *compl;
155 gchar label[32];
156
157 /* Create tree store, populate with contents of built-in and user-defined lists. */
158 cdlg->store = gtk_tree_store_new(1, G_TYPE_STRING);
159 gtk_tree_store_append(cdlg->store, &iter, NULL);
160 g_snprintf(label, sizeof label, _("Built-Ins (%u)"), g_list_length(cdlg->builtin));
161 gtk_tree_store_set(cdlg->store, &iter, 0, label, -1);
162 append_subtree_list(cdlg->builtin, cdlg->store, &iter);
163
164 gtk_tree_store_append(cdlg->store, &iter, NULL);
165 g_snprintf(label, sizeof label, _("User Defined (%u)"), g_list_length(cdlg->cmdseq));
166 gtk_tree_store_set(cdlg->store, &iter, 0, label, -1);
167 append_subtree_list(cdlg->cmdseq, cdlg->store, &iter);
168
169 cdlg->lstore = gtk_list_store_new(1, G_TYPE_STRING);
170 append_liststore_list(cdlg->builtin, cdlg->lstore);
171 append_liststore_list(cdlg->cmdseq, cdlg->lstore);
172
173 /* Create view, renderer, and column, and stuff it all together. */
174 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(cdlg->store));
175 cr = gtk_cell_renderer_text_new();
176 vc = gtk_tree_view_column_new_with_attributes("(Commands)", cr, "text", 0, NULL);
177 gtk_tree_view_append_column(GTK_TREE_VIEW(view), vc);
178 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
179
180 /* Expand whatever was previously expanded. */
181 if(cdlg->expanded[0])
182 {
183 path = gtk_tree_path_new_from_indices(0, -1);
184 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, FALSE);
185 gtk_tree_path_free(path);
186 }
187 if(cdlg->expanded[1])
188 {
189 path = gtk_tree_path_new_from_indices(1, -1);
190 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, FALSE);
191 gtk_tree_path_free(path);
192 }
193
194 /* Connect expand/collapse-tracking signals. */
195 g_signal_connect(G_OBJECT(view), "row_collapsed", G_CALLBACK(evt_row_collapsed_expanded), cdlg);
196 g_signal_connect(G_OBJECT(view), "row_expanded", G_CALLBACK(evt_row_collapsed_expanded), cdlg);
197
198 /* And track command selections, too. */
199 g_signal_connect(G_OBJECT(view), "cursor_changed", G_CALLBACK(evt_cursor_changed), cdlg);
200
201 /* Support accept on double-click, for speedier feel. */
202 g_signal_connect(G_OBJECT(view), "row_activated", G_CALLBACK(evt_row_activated), cdlg);
203
204 /* Create GtkEntryCompletion and link to tree. */
205 compl = gtk_entry_completion_new();
206 gtk_entry_completion_set_match_func(compl, cb_match_func, cdlg, NULL);
207 gtk_entry_completion_set_text_column(compl, 0);
208 gtk_entry_completion_set_model(compl, GTK_TREE_MODEL(cdlg->lstore));
209 gtk_entry_completion_set_inline_selection(compl, TRUE);
210 gtk_entry_set_completion(GTK_ENTRY(cdlg->entry), compl);
211
212 return view;
213 }
214
215 /* ----------------------------------------------------------------------------------------- */
216
217 /* 1998-09-27 - Open up a dialog where the user can select (or type) a command name.
218 ** The <cmdseq> argument should contain the current user-defined command sequences.
219 ** If it is NULL, the possibly-not-so-current ones in min->cfg.commands are used.
220 ** 1999-03-29 - Simplified semantics; now runs synchronously, and simply returns command name (or NULL).
221 */
csq_dialog_sync_new_wait(MainInfo * min,GHashTable * cmdseq)222 const gchar * csq_dialog_sync_new_wait(MainInfo *min, GHashTable *cmdseq)
223 {
224 CDlg *cdlg = &the_cdlg;
225 const gchar *ret = NULL;
226 GtkWidget *label, *scwin, *tree;
227
228 if(cmdseq == NULL) /* No alternative command sequence hash? */
229 cmdseq = min->cfg.commands.cmdseq;
230
231 cdlg->builtin = hash_to_list(min->cfg.commands.builtin);
232 cdlg->cmdseq = hash_to_list(cmdseq);
233 if(cdlg->cmd == NULL)
234 cdlg->cmd = g_string_new(NULL);
235
236 cdlg->clist = cdlg->citer = NULL;
237
238 cdlg->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
239 label = gtk_label_new(_("Select a command, or type part of its name."));
240 gtk_box_pack_start(GTK_BOX(cdlg->vbox), label, FALSE, FALSE, 0);
241 scwin = gtk_scrolled_window_new(NULL, NULL);
242 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
243 cdlg->entry = gui_dialog_entry_new();
244 if((tree = build_tree(min, cdlg)) != NULL)
245 {
246 gtk_container_add(GTK_CONTAINER(scwin), tree);
247 gtk_box_pack_start(GTK_BOX(cdlg->vbox), scwin, TRUE, TRUE, 0);
248 gtk_box_pack_start(GTK_BOX(cdlg->vbox), cdlg->entry, FALSE, FALSE, 0);
249
250 gtk_entry_set_text(GTK_ENTRY(cdlg->entry), cdlg->cmd->str);
251 gtk_editable_select_region(GTK_EDITABLE(cdlg->entry), 0, -1);
252
253 cdlg->dlg = dlg_dialog_sync_new(cdlg->vbox, _("Select Command"), NULL);
254 gtk_window_set_default_size(GTK_WINDOW(dlg_dialog_get_dialog(cdlg->dlg)), 320, 544);
255 gtk_widget_grab_focus(cdlg->entry);
256 if(dlg_dialog_sync_wait(cdlg->dlg) == DLG_POSITIVE)
257 {
258 g_string_assign(cdlg->cmd, gtk_entry_get_text(GTK_ENTRY(cdlg->entry)));
259 ret = cdlg->cmd->str;
260 }
261 dlg_dialog_sync_destroy(cdlg->dlg);
262 g_list_free(cdlg->builtin);
263 g_list_free(cdlg->cmdseq);
264 g_object_unref(cdlg->lstore);
265 }
266 return ret;
267 }
268