1 /*
2  *      envtree.c
3  *
4  *      Copyright 2010 Alexander Petukhov <devel(at)apetukhov.ru>
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License
17  *      along with this program; if not, write to the Free Software
18  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *      MA 02110-1301, USA.
20  */
21 
22 /*
23  * 		Working with the evironment variables tree view.
24  */
25 
26 #include <string.h>
27 
28 #ifdef HAVE_CONFIG_H
29 	#include "config.h"
30 #endif
31 #include <geanyplugin.h>
32 
33 extern GeanyPlugin		*geany_plugin;
34 
35 #include <gdk/gdkkeysyms.h>
36 
37 #include "envtree.h"
38 #include "dconfig.h"
39 
40 /* environment variables tree view columns */
41 enum
42 {
43    NAME,
44    VALUE,
45    LAST_VISIBLE,
46    N_COLUMNS
47 };
48 
49 static GtkListStore *store;
50 static GtkTreeModel *model;
51 static GtkWidget *tree;
52 
53 /* flag shows that the page is readonly (debug is running) */
54 static gboolean page_read_only = FALSE;
55 
56 /* flag shows we entered new env variable name and now entering its value */
57 static gboolean entering_new_var = FALSE;
58 
59 /* reference to the env tree view empty row */
60 static GtkTreeRowReference *empty_row = NULL;
61 
62 static GtkTreePath *being_edited_value = NULL;
63 
64 /* env variable name cloumn */
65 static GtkTreeViewColumn *column_name = NULL;
66 static GtkCellRenderer *renderer_name = NULL;
67 
68 /* env variable value cloumn */
69 static GtkTreeViewColumn *column_value = NULL;
70 static GtkCellRenderer *renderer_value = NULL;
71 
72 /*
73  * adds empty row to env tree view
74  */
add_empty_row(void)75 static void add_empty_row(void)
76 {
77 	GtkTreeIter empty;
78 	GtkTreePath *path;
79 
80 	if (empty_row)
81 		gtk_tree_row_reference_free(empty_row);
82 
83 	gtk_list_store_append (store, &empty);
84 	gtk_list_store_set (store, &empty,
85 		NAME, "",
86 		VALUE, "",
87 		-1);
88 
89 	/* remeber reference */
90 	path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &empty);
91 	empty_row = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
92 	gtk_tree_path_free(path);
93 }
94 
95 /*
96  * delete selected rows from env variables page
97  */
delete_selected_rows(void)98 static void delete_selected_rows(void)
99 {
100 	/* path to select after deleting finishes */
101 	GtkTreeRowReference *reference_to_select = NULL;
102 
103 	/* empty row path */
104 	GtkTreePath *empty_path = gtk_tree_row_reference_get_path(empty_row);
105 
106 	/* get selected rows */
107 	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
108 	GList *rows = gtk_tree_selection_get_selected_rows(selection, &model);
109 
110 	/* check whether only empty row was selected */
111 	if (1 != gtk_tree_selection_count_selected_rows(selection) ||
112 	    (rows && gtk_tree_path_compare((GtkTreePath*)rows->data, empty_path)))
113 	{
114 		GtkTreePath *path;
115 		/* get references to the selected rows and find out what to
116 		select after deletion */
117 		GList *references = NULL;
118 		GList *iter = rows;
119 		while (iter)
120 		{
121 			path = (GtkTreePath*)iter->data;
122 			if (!reference_to_select)
123 			{
124 				/* select upper sibling of the upper
125 				selected row that has unselected upper sibling */
126 				GtkTreePath *sibling = gtk_tree_path_copy(path);
127 				if(gtk_tree_path_prev(sibling))
128 				{
129 					if (!gtk_tree_selection_path_is_selected(selection, sibling))
130 						reference_to_select = gtk_tree_row_reference_new(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)), sibling);
131 				}
132 				else if (gtk_tree_path_next(sibling), gtk_tree_path_compare(path, sibling))
133 					reference_to_select = gtk_tree_row_reference_new(gtk_tree_view_get_model(GTK_TREE_VIEW(tree)), sibling);
134 			}
135 
136 			if (gtk_tree_path_compare(path, empty_path))
137 				references = g_list_prepend(references, gtk_tree_row_reference_new(model, path));
138 
139 			iter = iter->next;
140 		}
141 
142 		/* if all (with or without empty row) was selected - set empty row
143 		as a path to be selected after deleting */
144 		if (!reference_to_select)
145 			reference_to_select = gtk_tree_row_reference_copy (empty_row);
146 
147 		iter = g_list_reverse(references);
148 		while (iter)
149 		{
150 			GtkTreeIter titer;
151 			GtkTreeRowReference *reference = (GtkTreeRowReference*)iter->data;
152 			path = gtk_tree_row_reference_get_path(reference);
153 
154 			gtk_tree_model_get_iter(model, &titer, path);
155 			gtk_list_store_remove(store, &titer);
156 
157 			gtk_tree_path_free(path);
158 			iter = iter->next;
159 		}
160 
161 		/* set selection */
162 		gtk_tree_selection_unselect_all(selection);
163 		path = gtk_tree_row_reference_get_path(reference_to_select);
164 		gtk_tree_selection_select_path(selection, path);
165 		gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(tree), path, NULL, TRUE, 0.5, 0.5);
166 		gtk_tree_path_free(path);
167 
168 		/* free references list */
169 		g_list_foreach (references, (GFunc)gtk_tree_row_reference_free, NULL);
170 		g_list_free (references);
171 	}
172 
173 	/* free selection reference */
174 	gtk_tree_row_reference_free(reference_to_select);
175 
176 	gtk_tree_path_free(empty_path);
177 
178 	/* free rows list */
179 	g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
180 	g_list_free (rows);
181 }
182 
183 /*
184  * env tree key pressed handler
185  */
on_envtree_keypressed(GtkWidget * widget,GdkEvent * event,gpointer user_data)186 static gboolean on_envtree_keypressed(GtkWidget *widget, GdkEvent  *event, gpointer user_data)
187 {
188 	guint keyval;
189 
190 	/* do not allow deleting while debugging */
191 	if(page_read_only)
192 		return FALSE;
193 
194 	/* handling only Delete button pressing
195 	that means "delete selected rows" */
196 	keyval = ((GdkEventKey*)event)->keyval;
197 
198 	if (GDK_Delete == keyval)
199 	{
200 		delete_selected_rows();
201 		config_set_debug_changed();
202 	}
203 
204 	return GDK_Tab == keyval;
205 }
206 
207 /*
208  * env tree view value column render function
209  */
on_render_value(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)210 static void on_render_value(GtkTreeViewColumn *tree_column,
211 	 GtkCellRenderer *cell,
212 	 GtkTreeModel *tree_model,
213 	 GtkTreeIter *iter,
214 	 gpointer data)
215 {
216 	/* do not allow to edit value in read only mode */
217 	if (page_read_only)
218 	{
219 		g_object_set (cell, "editable", FALSE, NULL);
220 	}
221 	else
222 	{
223 		/* do not allow to edit value for empty row */
224 		GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
225 		GtkTreePath *empty_path = gtk_tree_row_reference_get_path(empty_row);
226 		gboolean empty = !gtk_tree_path_compare(path, empty_path);
227 		g_object_set (cell, "editable", entering_new_var || !empty, NULL);
228 		gtk_tree_path_free(path);
229 		gtk_tree_path_free(empty_path);
230 	}
231 }
232 
233 /*
234  * env tree view value changed handler
235  */
on_value_changed(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer user_data)236 static void on_value_changed(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data)
237 {
238 	gchar *striped;
239 	GtkTreeIter  iter;
240 	GtkTreePath *tree_path = gtk_tree_path_new_from_string (path);
241 	GtkTreePath *empty_path = gtk_tree_row_reference_get_path(empty_row);
242 	gboolean empty = !gtk_tree_path_compare(tree_path, empty_path);
243 	gtk_tree_path_free(empty_path);
244 
245 	gtk_tree_model_get_iter (
246 		 model,
247 		 &iter,
248 		 tree_path);
249 
250 	striped = g_strstrip(g_strdup(new_text));
251 	if (!strlen(striped))
252 	{
253 		/* if new value is empty string, if it's a new row - do nothig
254 		 otheerwise - offer to delete a variable */
255 		if (empty)
256 			gtk_list_store_set(store, &iter, NAME, "", -1);
257 		else
258 		{
259 			if (dialogs_show_question(_("Delete variable?")))
260 			{
261 				delete_selected_rows();
262 				config_set_debug_changed();
263 
264 				gtk_widget_grab_focus(tree);
265 			}
266 		}
267 	}
268 	else
269 	{
270 		/* if old variable - change value, otherwise - add another empty row below */
271 		gchar* oldvalue;
272 		gtk_tree_model_get (
273 			model,
274 			&iter,
275 			VALUE, &oldvalue,
276 		   -1);
277 
278 		if (strcmp(oldvalue, striped))
279 		{
280 			gtk_list_store_set(store, &iter, VALUE, striped, -1);
281 			if (empty)
282 				add_empty_row();
283 
284 			g_object_set (renderer_value, "editable", FALSE, NULL);
285 			config_set_debug_changed();
286 		}
287 
288 		g_free(oldvalue);
289 	}
290 
291 	if (empty)
292 		entering_new_var = FALSE;
293 
294 	gtk_tree_path_free(tree_path);
295 	g_free(striped);
296 
297 	gtk_tree_path_free(being_edited_value);
298 	being_edited_value = NULL;
299 }
300 
301 /*
302  * env tree view value editing started
303  */
on_value_editing_started(GtkCellRenderer * renderer,GtkCellEditable * editable,gchar * path,gpointer user_data)304 static void on_value_editing_started(GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path, gpointer user_data)
305 {
306 	being_edited_value = gtk_tree_path_new_from_string(path);
307 }
308 
309 /*
310  * env tree view value editing cancelled (Escape pressed)
311  */
on_value_editing_cancelled(GtkCellRenderer * renderer,gpointer user_data)312 static void on_value_editing_cancelled(GtkCellRenderer *renderer, gpointer user_data)
313 {
314 	/* check whether escape was pressed when editing
315 	new variable value cell */
316 	GtkTreePath *empty_path = gtk_tree_row_reference_get_path(empty_row);
317 	if(!gtk_tree_path_compare(being_edited_value, empty_path))
318 	{
319 		/* if so - clear name sell */
320 		GtkTreeIter iter;
321 		gtk_tree_model_get_iter(model, &iter, being_edited_value);
322 
323 		gtk_list_store_set (
324 			store,
325 			&iter,
326 			NAME, "",
327 		   -1);
328 
329 		entering_new_var = FALSE;
330 	}
331 
332 	g_object_set (renderer_value, "editable", FALSE, NULL);
333 
334 	gtk_tree_path_free(empty_path);
335 	gtk_tree_path_free(being_edited_value);
336 	being_edited_value = NULL;
337 }
338 
339 /*
340  * env tree view name changed hadler
341  */
on_name_changed(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer user_data)342 static void on_name_changed(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data)
343 {
344 	gchar *oldvalue, *striped;
345 	GtkTreeIter  iter;
346 	GtkTreePath *tree_path = gtk_tree_path_new_from_string (path);
347 	GtkTreePath *empty_path = gtk_tree_row_reference_get_path(empty_row);
348 	gboolean empty = !gtk_tree_path_compare(tree_path, empty_path);
349 
350 	gtk_tree_model_get_iter (
351 		 model,
352 		 &iter,
353 		 tree_path);
354 
355 	gtk_tree_model_get (
356 		model,
357 		&iter,
358 		NAME, &oldvalue,
359        -1);
360 
361 	striped = g_strstrip(g_strdup(new_text));
362 	if (!strlen(striped))
363 	{
364 		/* if name is empty - offer to delete variable */
365 		if (!empty && dialogs_show_question(_("Delete variable?")))
366 		{
367 			delete_selected_rows();
368 			config_set_debug_changed();
369 
370 			gtk_widget_grab_focus(tree);
371 		}
372 	}
373 	else if (strcmp(oldvalue, striped))
374 	{
375 		gtk_list_store_set(store, &iter, NAME, striped, -1);
376 		if (empty)
377 		{
378 			/* if it was a new row - move cursor to a value cell */
379 			entering_new_var = TRUE;
380 			gtk_tree_view_set_cursor_on_cell(GTK_TREE_VIEW(tree), tree_path, column_value, renderer_value, TRUE);
381 		}
382 		if (!empty)
383 		{
384 			config_set_debug_changed();
385 		}
386 	}
387 
388 	gtk_tree_path_free(tree_path);
389 	gtk_tree_path_free(empty_path);
390 	g_free(oldvalue);
391 	g_free(striped);
392 }
393 
394 /*
395  * create env tree view and return a widget
396  */
envtree_init(void)397 GtkWidget* envtree_init(void)
398 {
399 	GtkCellRenderer *renderer;
400 	GtkTreeViewColumn *column;
401 	GtkTreeSelection *selection;
402 
403 	/* (re)initialize globals in case plugin was reloaded but those not cleared */
404 	page_read_only = FALSE;
405 	entering_new_var = FALSE;
406 	empty_row = NULL;
407 	being_edited_value = NULL;
408 
409 	store = gtk_list_store_new (
410 		N_COLUMNS,
411 		G_TYPE_STRING,
412 		G_TYPE_STRING,
413 		G_TYPE_STRING);
414 	model = GTK_TREE_MODEL(store);
415 	tree = gtk_tree_view_new_with_model (model);
416 	g_object_unref(store);
417 
418 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), TRUE);
419 	g_signal_connect(G_OBJECT(tree), "key-press-event", G_CALLBACK (on_envtree_keypressed), NULL);
420 
421 	renderer_name = gtk_cell_renderer_text_new ();
422 	g_object_set (renderer_name, "editable", TRUE, NULL);
423 	g_signal_connect (G_OBJECT (renderer_name), "edited", G_CALLBACK (on_name_changed), NULL);
424 	column_name = gtk_tree_view_column_new_with_attributes (_("Name"), renderer_name, "text", NAME, NULL);
425 	gtk_tree_view_column_set_resizable (column_name, TRUE);
426 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column_name);
427 
428 	renderer_value = gtk_cell_renderer_text_new ();
429 	column_value = gtk_tree_view_column_new_with_attributes (_("Value"), renderer_value, "text", VALUE, NULL);
430 	g_signal_connect (G_OBJECT (renderer_value), "edited", G_CALLBACK (on_value_changed), NULL);
431 	g_signal_connect (G_OBJECT (renderer_value), "editing-started", G_CALLBACK (on_value_editing_started), NULL);
432 	g_signal_connect (G_OBJECT (renderer_value), "editing-canceled", G_CALLBACK (on_value_editing_cancelled), NULL);
433 	gtk_tree_view_column_set_cell_data_func(column_value, renderer_value, on_render_value, NULL, NULL);
434 	gtk_tree_view_column_set_resizable (column_value, TRUE);
435 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column_value);
436 
437 	/* Last invisible column */
438 	renderer = gtk_cell_renderer_text_new ();
439 	column = gtk_tree_view_column_new_with_attributes ("", renderer, "text", LAST_VISIBLE, NULL);
440 	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
441 
442 	/* add empty row */
443 	add_empty_row();
444 
445 	/* set multiple selection */
446 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
447 	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
448 
449 	return tree;
450 }
451 
452 /*
453  * deallocate data
454  */
envtree_destroy(void)455 void envtree_destroy(void)
456 {
457 	gtk_tree_row_reference_free(empty_row);
458 	empty_row = NULL;
459 }
460 
461 /*
462  * clear tree
463  */
envtree_clear(void)464 void envtree_clear(void)
465 {
466 	gtk_list_store_clear(store);
467 	add_empty_row();
468 }
469 
470 /*
471  * get list of environment variables
472  */
envpage_get_environment(void)473 GList* envpage_get_environment(void)
474 {
475 	GList *env = NULL;
476 
477 	GtkTreeIter iter;
478 	gtk_tree_model_get_iter_first(model, &iter);
479 	do
480 	{
481 		gchar *name, *value;
482 		gtk_tree_model_get (
483 			model,
484 			&iter,
485 			NAME, &name,
486 			VALUE, &value,
487 		   -1);
488 
489 		 if (strlen(name))
490 		 {
491 			env = g_list_append(env, name);
492 			env = g_list_append(env, value);
493 		 }
494 	}
495 	while (gtk_tree_model_iter_next(model, &iter));
496 
497 	return env;
498 }
499 
500 /*
501  * disable interacting for the time of debugging
502  */
envtree_set_readonly(gboolean readonly)503 void envtree_set_readonly(gboolean readonly)
504 {
505 	g_object_set (renderer_name, "editable", !readonly, NULL);
506 	page_read_only = readonly;
507 }
508 
509 /*
510  * add environment variable
511  */
envtree_add_environment(const gchar * name,const gchar * value)512 void envtree_add_environment(const gchar *name, const gchar *value)
513 {
514 	GtkTreeIter iter;
515 	gtk_list_store_prepend(store, &iter);
516 	gtk_list_store_set(store, &iter, NAME, name, VALUE, value, -1);
517 }
518