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