SF.net SVN: geany:[5676] trunk

colombanw at users.sourceforge.net colombanw at xxxxx
Sat Apr 2 18:36:35 UTC 2011


Revision: 5676
          http://geany.svn.sourceforge.net/geany/?rev=5676&view=rev
Author:   colombanw
Date:     2011-04-02 18:36:35 +0000 (Sat, 02 Apr 2011)

Log Message:
-----------
Improve the `Set Custom Commands` dialog

Modified Paths:
--------------
    trunk/ChangeLog
    trunk/doc/geany.html
    trunk/doc/geany.txt
    trunk/src/tools.c

Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2011-04-01 20:52:31 UTC (rev 5675)
+++ trunk/ChangeLog	2011-04-02 18:36:35 UTC (rev 5676)
@@ -1,3 +1,9 @@
+2011-04-01  Colomban Wendling  <colomban(at)geany(dot)org>
+
+ * src/tools.c, doc/geany.txt, doc/geany.html:
+   Improve the `Set Custom Commands` dialog.
+
+
 2011-04-01  Nick Treleaven  <nick(dot)treleaven(at)btinternet(dot)com>
 
  * src/interface.c, src/interface.h, geany.glade:

Modified: trunk/doc/geany.html
===================================================================
--- trunk/doc/geany.html	2011-04-01 20:52:31 UTC (rev 5675)
+++ trunk/doc/geany.html	2011-04-02 18:36:35 UTC (rev 5676)
@@ -1324,10 +1324,9 @@
 output.</p>
 <p>If there is no selection, the whole current line is used instead.</p>
 <p>To add a custom command, use the <em>Send Selection to->Set Custom
-Commands</em> menu item. Click on <em>Add</em> to get a new text entry and type
-the command. You can also specify some command line options. To
-delete a command, just clear the text entry and press OK. It will be
-deleted automatically.</p>
+Commands</em> menu item. Click on <em>Add</em> to get a new item and type the
+command. You can also specify some command line options. Empty
+commands are not saved.</p>
 <p>Normal shell quoting is supported, so you can do things like:</p>
 <ul class="simple">
 <li><tt class="docutils literal"><span class="pre">sed</span> <span class="pre">'s/\./(dot)/g'</span></tt></li>

Modified: trunk/doc/geany.txt
===================================================================
--- trunk/doc/geany.txt	2011-04-01 20:52:31 UTC (rev 5675)
+++ trunk/doc/geany.txt	2011-04-02 18:36:35 UTC (rev 5676)
@@ -883,10 +883,9 @@
 If there is no selection, the whole current line is used instead.
 
 To add a custom command, use the *Send Selection to->Set Custom
-Commands* menu item. Click on *Add* to get a new text entry and type
-the command. You can also specify some command line options. To
-delete a command, just clear the text entry and press OK. It will be
-deleted automatically.
+Commands* menu item. Click on *Add* to get a new item and type the
+command. You can also specify some command line options. Empty
+commands are not saved.
 
 Normal shell quoting is supported, so you can do things like:
 

Modified: trunk/src/tools.c
===================================================================
--- trunk/src/tools.c	2011-04-01 20:52:31 UTC (rev 5675)
+++ trunk/src/tools.c	2011-04-02 18:36:35 UTC (rev 5676)
@@ -53,46 +53,132 @@
 #include "dialogs.h"
 
 
+enum
+{
+	CC_COLUMN_ID,
+	CC_COLUMN_STATUS,
+	CC_COLUMN_TOOLTIP,
+	CC_COLUMN_CMD,
+	CC_COLUMN_EDITABLE,
+	CC_COLUMN_ELLIPSIZE,
+	CC_COLUMN_COUNT
+};
+
 /* custom commands code*/
 struct cc_dialog
 {
 	gint count;
-	GtkWidget *box;
+	GtkWidget *view;
+	GtkTreeViewColumn *edit_column;
+	GtkListStore *store;
+	GtkTreeSelection *selection;
+	GtkWidget *button_add;
+	GtkWidget *button_remove;
+	GtkWidget *button_up;
+	GtkWidget *button_down;
 };
 
 static gboolean cc_error_occurred = FALSE;
 static gboolean cc_reading_finished = FALSE;
 static GString *cc_buffer;
 
-static void cc_add_command(struct cc_dialog *cc, gint idx)
+
+/* update STATUS and TOOLTIP columns according to cmd */
+static void cc_dialog_update_row_status(GtkListStore *store, GtkTreeIter *iter, const gchar *cmd)
 {
-	GtkWidget *label, *entry, *hbox;
-	gchar str[6];
+	GError *err = NULL;
+	const gchar *stock_id;
+	gchar *tooltip = NULL;
 
-	hbox = gtk_hbox_new(FALSE, 5);
-	g_snprintf(str, 5, "%d:", cc->count);
-	label = gtk_label_new(str);
+	if (! NZV(cmd) || g_shell_parse_argv(cmd, NULL, NULL, &err))
+		stock_id = GTK_STOCK_YES;
+	else
+	{
+		stock_id = GTK_STOCK_NO;
+		tooltip = g_strdup_printf(_("Command cannot be parsed: %s"), err->message);
+		g_error_free(err);
+	}
 
-	entry = gtk_entry_new();
-	if (idx >= 0)
-		gtk_entry_set_text(GTK_ENTRY(entry), ui_prefs.custom_commands[idx]);
-	ui_entry_add_clear_icon(GTK_ENTRY(entry));
-	gtk_entry_set_max_length(GTK_ENTRY(entry), 255);
-	gtk_entry_set_width_chars(GTK_ENTRY(entry), 30);
-	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
-	gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
-	gtk_widget_show_all(hbox);
-	gtk_container_add(GTK_CONTAINER(cc->box), hbox);
+	gtk_list_store_set(store, iter, CC_COLUMN_STATUS, stock_id, CC_COLUMN_TOOLTIP, tooltip, -1);
+	g_free(tooltip);
+}
+
+
+/* adds a new row for custom command @p idx, or an new empty one if < 0 */
+static void cc_dialog_add_command(struct cc_dialog *cc, gint idx, gboolean start_editing)
+{
+	GtkTreeIter iter;
+	const gchar *cmd;
+
+	cmd = (idx >= 0) ? ui_prefs.custom_commands[idx] : NULL;
+
+	gtk_list_store_append(cc->store, &iter);
+	gtk_list_store_set(cc->store, &iter, CC_COLUMN_ID, cc->count, CC_COLUMN_CMD, cmd,
+			CC_COLUMN_EDITABLE, TRUE, CC_COLUMN_ELLIPSIZE, PANGO_ELLIPSIZE_END, -1);
+	cc_dialog_update_row_status(cc->store, &iter, cmd);
 	cc->count++;
+
+	if (start_editing)
+	{
+		GtkTreePath *path;
+
+		gtk_widget_grab_focus(cc->view);
+		path = gtk_tree_model_get_path(GTK_TREE_MODEL(cc->store), &iter);
+		gtk_tree_view_set_cursor(GTK_TREE_VIEW(cc->view), path, cc->edit_column, TRUE);
+		gtk_tree_path_free(path);
+	}
 }
 
 
-static void cc_on_custom_commands_dlg_add_clicked(GtkToolButton *toolbutton, struct cc_dialog *cc)
+static void cc_on_dialog_add_clicked(GtkButton *button, struct cc_dialog *cc)
 {
-	cc_add_command(cc, -1);
+	cc_dialog_add_command(cc, -1, TRUE);
 }
 
 
+static void cc_on_dialog_remove_clicked(GtkButton *button, struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+
+	if (gtk_tree_selection_get_selected(cc->selection, NULL, &iter))
+		gtk_list_store_remove(cc->store, &iter);
+}
+
+
+static void cc_on_dialog_move_up_clicked(GtkButton *button, struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+
+	if (gtk_tree_selection_get_selected(cc->selection, NULL, &iter))
+	{
+		GtkTreePath *path;
+		GtkTreeIter prev;
+
+		path = gtk_tree_model_get_path(GTK_TREE_MODEL(cc->store), &iter);
+		if (gtk_tree_path_prev(path) &&
+			gtk_tree_model_get_iter(GTK_TREE_MODEL(cc->store), &prev, path))
+		{
+			gtk_list_store_move_before(cc->store, &iter, &prev);
+		}
+		gtk_tree_path_free(path);
+	}
+}
+
+
+static void cc_on_dialog_move_down_clicked(GtkButton *button, struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+
+	if (gtk_tree_selection_get_selected(cc->selection, NULL, &iter))
+	{
+		GtkTreeIter next = iter;
+
+		if (gtk_tree_model_iter_next(GTK_TREE_MODEL(cc->store), &next))
+			gtk_list_store_move_after(cc->store, &iter, &next);
+	}
+}
+
+
 static gboolean cc_iofunc(GIOChannel *ioc, GIOCondition cond, gpointer data)
 {
 	if (cond & (G_IO_IN | G_IO_PRI))
@@ -300,15 +386,104 @@
 }
 
 
+static void cc_dialog_on_command_edited(GtkCellRendererText *renderer, gchar *path, gchar *text,
+		struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+
+	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(cc->store), &iter, path);
+	gtk_list_store_set(cc->store, &iter, CC_COLUMN_CMD, text, -1);
+	cc_dialog_update_row_status(cc->store, &iter, text);
+}
+
+
+/* re-compute IDs to reflect the current store state */
+static void cc_dialog_update_ids(struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+
+	cc->count = 1;
+	if (! gtk_tree_model_get_iter_first(GTK_TREE_MODEL(cc->store), &iter))
+		return;
+
+	do
+	{
+		gtk_list_store_set(cc->store, &iter, CC_COLUMN_ID, cc->count, -1);
+		cc->count++;
+	}
+	while (gtk_tree_model_iter_next(GTK_TREE_MODEL(cc->store), &iter));
+}
+
+
+/* update sensitiveness of the buttons according to the selection */
+static void cc_dialog_update_sensitive(struct cc_dialog *cc)
+{
+	GtkTreeIter iter;
+	gboolean has_selection = FALSE;
+	gboolean first_selected = FALSE;
+	gboolean last_selected = FALSE;
+
+	if ((has_selection = gtk_tree_selection_get_selected(cc->selection, NULL, &iter)))
+	{
+		GtkTreePath *path;
+		GtkTreePath *copy;
+
+		path = gtk_tree_model_get_path(GTK_TREE_MODEL(cc->store), &iter);
+		copy = gtk_tree_path_copy(path);
+		first_selected = ! gtk_tree_path_prev(copy);
+		gtk_tree_path_free(copy);
+		gtk_tree_path_next(path);
+		last_selected = ! gtk_tree_model_get_iter(GTK_TREE_MODEL(cc->store), &iter, path);
+		gtk_tree_path_free(path);
+	}
+
+	gtk_widget_set_sensitive(cc->button_remove, has_selection);
+	gtk_widget_set_sensitive(cc->button_up, has_selection && ! first_selected);
+	gtk_widget_set_sensitive(cc->button_down, has_selection && ! last_selected);
+}
+
+
+static void cc_dialog_on_tree_selection_changed(GtkTreeSelection *selection, struct cc_dialog *cc)
+{
+	cc_dialog_update_sensitive(cc);
+}
+
+
+static void cc_dialog_on_row_inserted(GtkTreeModel *model, GtkTreePath  *path, GtkTreeIter *iter,
+		struct cc_dialog *cc)
+{
+	cc_dialog_update_ids(cc);
+	cc_dialog_update_sensitive(cc);
+}
+
+
+static void cc_dialog_on_row_deleted(GtkTreeModel *model, GtkTreePath  *path, struct cc_dialog *cc)
+{
+	cc_dialog_update_ids(cc);
+	cc_dialog_update_sensitive(cc);
+}
+
+
+static void cc_dialog_on_rows_reordered(GtkTreeModel *model, GtkTreePath  *path, GtkTreeIter *iter,
+		gpointer new_order, struct cc_dialog *cc)
+{
+	cc_dialog_update_ids(cc);
+	cc_dialog_update_sensitive(cc);
+}
+
+
 static void cc_show_dialog_custom_commands(void)
 {
-	GtkWidget *dialog, *label, *vbox, *button;
+	GtkWidget *dialog, *label, *vbox, *scroll, *buttonbox;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
 	guint i;
 	struct cc_dialog cc;
 
 	dialog = gtk_dialog_new_with_buttons(_("Set Custom Commands"), GTK_WINDOW(main_widgets.window),
 						GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 						GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 300); /* give a reasonable minimal default size */
 	vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
 	gtk_box_set_spacing(GTK_BOX(vbox), 6);
 	gtk_widget_set_name(dialog, "GeanyDialog");
@@ -316,74 +491,127 @@
 	label = gtk_label_new(_("You can send the current selection to any of these commands and the output of the command replaces the current selection."));
 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
-	gtk_container_add(GTK_CONTAINER(vbox), label);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
 
 	cc.count = 1;
-	cc.box = gtk_vbox_new(FALSE, 0);
-	gtk_container_add(GTK_CONTAINER(vbox), cc.box);
+	cc.store = gtk_list_store_new(CC_COLUMN_COUNT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING,
+			G_TYPE_STRING, G_TYPE_BOOLEAN, PANGO_TYPE_ELLIPSIZE_MODE);
+	cc.view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(cc.store));
+	gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(cc.view), CC_COLUMN_TOOLTIP);
+	gtk_tree_view_set_reorderable(GTK_TREE_VIEW(cc.view), TRUE);
+	cc.selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cc.view));
+	/* ID column */
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("ID"), renderer, "text", CC_COLUMN_ID, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(cc.view), column);
+	/* command column, holding status and command display */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Command"));
+	renderer = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, renderer, FALSE);
+	gtk_tree_view_column_set_attributes(column, renderer, "stock-id", CC_COLUMN_STATUS, NULL);
+	renderer = gtk_cell_renderer_text_new();
+	g_signal_connect(renderer, "edited", G_CALLBACK(cc_dialog_on_command_edited), &cc);
+	gtk_tree_view_column_pack_start(column, renderer, TRUE);
+	gtk_tree_view_column_set_attributes(column, renderer, "text", CC_COLUMN_CMD,
+			"editable", CC_COLUMN_EDITABLE, "ellipsize", CC_COLUMN_ELLIPSIZE, NULL);
+	cc.edit_column = column;
+	gtk_tree_view_append_column(GTK_TREE_VIEW(cc.view), column);
 
-	if (ui_prefs.custom_commands == NULL || g_strv_length(ui_prefs.custom_commands) == 0)
+	scroll = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC,
+			GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(scroll), cc.view);
+	gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
+
+	if (ui_prefs.custom_commands != NULL)
 	{
-		cc_add_command(&cc, -1);
-	}
-	else
-	{
+		GtkTreeIter iter;
 		guint len = g_strv_length(ui_prefs.custom_commands);
+
 		for (i = 0; i < len; i++)
 		{
-			if (ui_prefs.custom_commands[i][0] == '\0')
+			if (! NZV(ui_prefs.custom_commands[i]))
 				continue; /* skip empty fields */
 
-			cc_add_command(&cc, i);
+			cc_dialog_add_command(&cc, i, FALSE);
 		}
+
+		/* focus the first row if any */
+		if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(cc.store), &iter))
+		{
+			GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(cc.store), &iter);
+
+			gtk_tree_view_set_cursor(GTK_TREE_VIEW(cc.view), path, cc.edit_column, FALSE);
+			gtk_tree_path_free(path);
+		}
 	}
 
-	button = gtk_button_new_from_stock("gtk-add");
-	g_signal_connect(button, "clicked", G_CALLBACK(cc_on_custom_commands_dlg_add_clicked), &cc);
-	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
+	buttonbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(buttonbox), 6);
+	gtk_box_pack_start(GTK_BOX(vbox), buttonbox, FALSE, FALSE, 0);
+	cc.button_add = gtk_button_new_from_stock(GTK_STOCK_ADD);
+	g_signal_connect(cc.button_add, "clicked", G_CALLBACK(cc_on_dialog_add_clicked), &cc);
+	gtk_container_add(GTK_CONTAINER(buttonbox), cc.button_add);
+	cc.button_remove = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
+	g_signal_connect(cc.button_remove, "clicked", G_CALLBACK(cc_on_dialog_remove_clicked), &cc);
+	gtk_container_add(GTK_CONTAINER(buttonbox), cc.button_remove);
+	cc.button_up = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
+	g_signal_connect(cc.button_up, "clicked", G_CALLBACK(cc_on_dialog_move_up_clicked), &cc);
+	gtk_container_add(GTK_CONTAINER(buttonbox), cc.button_up);
+	cc.button_down = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
+	g_signal_connect(cc.button_down, "clicked", G_CALLBACK(cc_on_dialog_move_down_clicked), &cc);
+	gtk_container_add(GTK_CONTAINER(buttonbox), cc.button_down);
 
+	cc_dialog_update_sensitive(&cc);
+
+	/* only connect the selection signal when all other cc_dialog fields are set */
+	g_signal_connect(cc.selection, "changed", G_CALLBACK(cc_dialog_on_tree_selection_changed), &cc);
+	g_signal_connect(cc.store, "row-inserted", G_CALLBACK(cc_dialog_on_row_inserted), &cc);
+	g_signal_connect(cc.store, "row-deleted", G_CALLBACK(cc_dialog_on_row_deleted), &cc);
+	g_signal_connect(cc.store, "rows-reordered", G_CALLBACK(cc_dialog_on_rows_reordered), &cc);
+
 	gtk_widget_show_all(vbox);
 
 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
 	{
-		/* get all hboxes which contain a label and an entry element */
-		GList *children = gtk_container_get_children(GTK_CONTAINER(cc.box));
-		GList *node, *list;
 		GSList *result_list = NULL;
-		gint j = 0;
 		gint len = 0;
 		gchar **result = NULL;
-		const gchar *text;
+		GtkTreeIter iter;
 
-		foreach_list(node, children)
+		if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(cc.store), &iter))
 		{
-			/* get the contents of each hbox */
-			list = gtk_container_get_children(GTK_CONTAINER(node->data));
+			do
+			{
+				gchar *cmd;
 
-			/* first element of the list is the label, so skip it and get the entry element */
-			text = gtk_entry_get_text(GTK_ENTRY(list->next->data));
-
-			/* if the content of the entry is non-empty, add it to the result array */
-			if (text[0] != '\0')
-			{
-				result_list = g_slist_prepend(result_list, g_strdup(text));
-				len++;
+				gtk_tree_model_get(GTK_TREE_MODEL(cc.store), &iter, CC_COLUMN_CMD, &cmd, -1);
+				if (NZV(cmd))
+				{
+					result_list = g_slist_prepend(result_list, cmd);
+					len++;
+				}
+				else
+					g_free(cmd);
 			}
-			g_list_free(list);
+			while (gtk_tree_model_iter_next(GTK_TREE_MODEL(cc.store), &iter));
 		}
 		result_list = g_slist_reverse(result_list);
-		/* create a new null-terminated array but only if there any commands defined */
+		/* create a new null-terminated array but only if there is any commands defined */
 		if (len > 0)
 		{
+			gint j = 0;
+			GSList *node;
+
 			result = g_new(gchar*, len + 1);
-			while (result_list != NULL)
+			foreach_list(node, result_list)
 			{
-				result[j] = (gchar*) result_list->data;
-
-				result_list = result_list->next;
+				result[j] = (gchar*) node->data;
 				j++;
 			}
-			result[len] = NULL; /* null-terminate the array */
+			result[j] = NULL; /* null-terminate the array */
 		}
 		/* set the new array */
 		g_strfreev(ui_prefs.custom_commands);
@@ -392,7 +620,6 @@
 		tools_create_insert_custom_command_menu_items();
 
 		g_slist_free(result_list);
-		g_list_free(children);
 	}
 	gtk_widget_destroy(dialog);
 }


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



More information about the Commits mailing list