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