1 /**
2 * @file gui-snippets.c
3 * @brief Handle snippets and provide edit/new/delete function
4 *
5 * Copyright (C) 2009 Gummi Developers
6 * All Rights reserved.
7 *
8 * Permission is hereby granted, free of charge, to any person
9 * obtaining a copy of this software and associated documentation
10 * files (the "Software"), to deal in the Software without
11 * restriction, including without limitation the rights to use,
12 * copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following
15 * conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 */
29
30 #include "gui/gui-snippets.h"
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib.h>
36 #include <gtk/gtk.h>
37 #include <gtksourceview/gtksource.h>
38
39 #include "constants.h"
40 #include "environment.h"
41 #include "gui/gui-main.h"
42 #include "snippets.h"
43
44 extern Gummi* gummi;
45 extern GummiGui* gui;
46
47 static void snippetsgui_insert_at_current(GuSnippetsGui* sc, gchar* text);
48
snippetsgui_init(GtkWindow * mainwindow)49 GuSnippetsGui* snippetsgui_init (GtkWindow* mainwindow) {
50 GuSnippetsGui* s = g_new0 (GuSnippetsGui, 1);
51 GtkSourceLanguageManager* manager = NULL;
52 GtkSourceLanguage* lang = NULL;
53 gchar* lang_dir = NULL;
54 gchar** langs = NULL;
55 gchar** new_langs = NULL;
56 gint len = 0, i = 0;
57
58 GtkBuilder* builder = gtk_builder_new ();
59 gchar* ui = g_build_filename (GUMMI_DATA, "ui", "snippets.glade", NULL);
60 gtk_builder_add_from_file (builder, ui, NULL);
61 gtk_builder_set_translation_domain (builder, C_PACKAGE);
62 g_free (ui);
63
64 s->snippetswindow =
65 GTK_WINDOW (gtk_builder_get_object (builder, "snippetswindow"));
66 s->snippets_tree_view =
67 GTK_TREE_VIEW (gtk_builder_get_object (builder, "snippets_tree_view"));
68 s->snippet_scroll =
69 GTK_SCROLLED_WINDOW(gtk_builder_get_object (builder, "snippet_scroll"));
70 s->tab_trigger_entry =
71 GTK_ENTRY (gtk_builder_get_object (builder, "tab_trigger_entry"));
72 s->accelerator_entry =
73 GTK_ENTRY (gtk_builder_get_object (builder, "accelerator_entry"));
74 s->list_snippets =
75 GTK_LIST_STORE (gtk_builder_get_object (builder, "list_snippets"));
76 s->snippet_renderer = GTK_CELL_RENDERER_TEXT
77 (gtk_builder_get_object (builder, "snippet_renderer"));
78 s->button_new =
79 GTK_BUTTON (gtk_builder_get_object (builder, "button_new_snippet"));
80 s->button_remove =
81 GTK_BUTTON (gtk_builder_get_object (builder, "button_remove_snippet"));
82
83 /* Initialize GtkSourceView */
84 manager = gtk_source_language_manager_new ();
85 lang_dir = g_build_filename (GUMMI_DATA, "snippets", NULL);
86 langs = g_strdupv ((gchar**)gtk_source_language_manager_get_search_path (
87 manager));
88 len = g_strv_length (langs);
89 new_langs = g_new0 (gchar*, len + 2);
90 for (i = 0; i < len; ++i)
91 new_langs[i] = langs[i];
92 new_langs[len] = lang_dir;
93 gtk_source_language_manager_set_search_path (manager, new_langs);
94 lang = gtk_source_language_manager_get_language (manager, "snippets");
95 g_strfreev (langs);
96 g_free (new_langs);
97 g_free (lang_dir);
98
99 s->buffer = gtk_source_buffer_new_with_language (lang);
100 s->view = GTK_SOURCE_VIEW (gtk_source_view_new_with_buffer (s->buffer));
101 gtk_container_add (GTK_CONTAINER (s->snippet_scroll), GTK_WIDGET (s->view));
102
103 snippetsgui_load_snippets (s);
104
105 g_signal_connect (s->view, "key-release-event",
106 G_CALLBACK (on_snippet_source_buffer_key_release), NULL);
107
108 gtk_window_set_transient_for (s->snippetswindow, mainwindow);
109 gtk_builder_connect_signals (builder, NULL);
110
111 return s;
112 }
113
snippetsgui_main(GuSnippetsGui * s)114 void snippetsgui_main (GuSnippetsGui* s) {
115 gtk_widget_show_all (GTK_WIDGET (s->snippetswindow));
116 }
117
snippetsgui_insert_at_current(GuSnippetsGui * sc,gchar * text)118 static void snippetsgui_insert_at_current(GuSnippetsGui* sc, gchar* text) {
119 GtkTextIter start;
120 GtkTextBuffer* buffer = GTK_TEXT_BUFFER(sc->buffer);
121 GtkTextMark* mark = gtk_text_buffer_get_insert (buffer);
122 gtk_text_buffer_get_iter_at_mark (buffer, &start, mark);
123 gtk_text_buffer_insert (buffer, &start, text, -1);
124 }
125
snippetsgui_load_snippets(GuSnippetsGui * s)126 void snippetsgui_load_snippets (GuSnippetsGui* s) {
127 slist* current = gummi->snippets->head;
128 GtkTreeIter iter;
129 gchar** configs = NULL;
130
131 gtk_list_store_clear (s->list_snippets);
132 while (current) {
133 if (current->second) {
134 gtk_list_store_append (s->list_snippets, &iter);
135 configs = g_strsplit (current->first, ",", 0);
136 gtk_list_store_set (s->list_snippets, &iter, 0, configs[2],
137 1, configs[0],
138 2, configs[1], -1);
139 g_strfreev (configs);
140 }
141 current = current->next;
142 }
143 }
144
snippetsgui_move_cursor_to_row(GuSnippetsGui * s,gint row)145 void snippetsgui_move_cursor_to_row (GuSnippetsGui* s, gint row) {
146 gchar* path_str = NULL;
147 GtkTreePath* path = NULL;
148 GtkTreeViewColumn* col = NULL;
149
150 path_str = g_strdup_printf ("%d", (row >= 0)? row: 0);
151 path = gtk_tree_path_new_from_string (path_str);
152 col = gtk_tree_view_get_column (s->snippets_tree_view, 0);
153 gtk_tree_view_set_cursor (s->snippets_tree_view, path, col, FALSE);
154 gtk_tree_path_free (path);
155 g_free (path_str);
156 }
157
snippetsgui_update_snippet(GuSnippets * sc)158 void snippetsgui_update_snippet (GuSnippets* sc) {
159 GuSnippetsGui* s = gui->snippetsgui;
160 const gchar* new_accel = NULL;
161 const gchar* new_key = NULL;
162 gchar** configs = NULL;
163 GtkTreeIter iter;
164 GtkTreeModel* model =NULL;
165 GtkTreeSelection* selection = NULL;
166 slist* target = s->current;
167
168 configs = g_strsplit (target->first, ",", 0);
169 new_key = gtk_entry_get_text (s->tab_trigger_entry);
170 new_accel = gtk_entry_get_text (s->accelerator_entry);
171 model = GTK_TREE_MODEL (gtk_tree_view_get_model (s->snippets_tree_view));
172 selection = gtk_tree_view_get_selection (s->snippets_tree_view);
173 gtk_tree_selection_get_selected (selection, &model, &iter);
174
175 g_free (target->first);
176 target->first = g_strdup_printf("%s,%s,%s", new_key, new_accel, configs[2]);
177 gtk_list_store_set (s->list_snippets, &iter, 0, configs[2],
178 1, new_key,
179 2, new_accel, -1);
180
181 /* Disconnect old accelerator */
182 snippets_accel_disconnect (sc, configs[0]);
183
184 /* Update acceleartor */
185 if (strlen (new_accel)) {
186 GClosure* new_closure = NULL;;
187 GdkModifierType mod;
188 guint keyval = 0;
189
190 Tuple2* data = g_new0 (Tuple2, 1);
191 Tuple2* new_closure_data = g_new0 (Tuple2, 1);
192
193 data->first = (gpointer)sc;
194 data->second = (gpointer)g_strdup (new_key);
195
196 new_closure = g_cclosure_new(G_CALLBACK(snippets_accel_cb), data, NULL);
197 new_closure_data->first = data->second;
198 new_closure_data->second = new_closure;
199
200 /* Connect new accelerator */
201 gtk_accelerator_parse (new_accel, &keyval, &mod);
202 sc->closure_data = g_list_append (sc->closure_data, new_closure_data);
203 snippets_accel_connect (sc, keyval, mod, new_closure);
204 }
205 g_strfreev (configs);
206 }
207
208 G_MODULE_EXPORT
on_snippetsgui_close_clicked(GtkWidget * widget,void * user)209 void on_snippetsgui_close_clicked (GtkWidget* widget, void* user) {
210 gtk_widget_hide (GTK_WIDGET (gui->snippetsgui->snippetswindow));
211 snippets_save (gummi->snippets);
212 }
213
214 G_MODULE_EXPORT
on_snippetsgui_reset_clicked(GtkWidget * widget,void * user)215 void on_snippetsgui_reset_clicked (GtkWidget* widget, void* user) {
216 snippets_set_default (gummi->snippets);
217 snippetsgui_load_snippets (gui->snippetsgui);
218 snippetsgui_move_cursor_to_row (gui->snippetsgui, 0);
219 }
220
221 G_MODULE_EXPORT
on_snippetsgui_selected_text_clicked(GtkWidget * widget,void * user)222 void on_snippetsgui_selected_text_clicked (GtkWidget* widget, void* user) {
223 snippetsgui_insert_at_current(gui->snippetsgui, "$SELECTED_TEXT");
224 }
225
226 G_MODULE_EXPORT
on_snippetsgui_filename_clicked(GtkWidget * widget,void * user)227 void on_snippetsgui_filename_clicked (GtkWidget* widget, void* user) {
228 snippetsgui_insert_at_current(gui->snippetsgui, "$FILENAME");
229
230 }
231
232 G_MODULE_EXPORT
on_snippetsgui_basename_clicked(GtkWidget * widget,void * user)233 void on_snippetsgui_basename_clicked (GtkWidget* widget, void* user) {
234 snippetsgui_insert_at_current(gui->snippetsgui, "$BASENAME");
235 }
236
237 G_MODULE_EXPORT
on_button_new_snippet_clicked(GtkWidget * widget,void * user)238 void on_button_new_snippet_clicked (GtkWidget* widget, void* user) {
239 GuSnippetsGui* s = gui->snippetsgui;
240 GtkTreeIter iter;
241 GtkTreeModel *model = NULL;
242 GtkTreePath *path = NULL;
243 GtkTreeViewColumn *col = NULL;
244
245 gtk_list_store_append (s->list_snippets, &iter);
246 g_object_set (s->snippet_renderer, "editable", TRUE, NULL);
247
248 col = gtk_tree_view_get_column (s->snippets_tree_view, 0);
249 model = gtk_tree_view_get_model (s->snippets_tree_view);
250 path = gtk_tree_model_get_path (model, &iter);
251
252 gtk_widget_set_sensitive (GTK_WIDGET (s->button_new), FALSE);
253 gtk_widget_set_sensitive (GTK_WIDGET (s->button_remove), FALSE);
254
255 gtk_tree_view_set_cursor(s->snippets_tree_view, path, col, TRUE);
256
257 gtk_tree_path_free (path);
258 }
259
260 G_MODULE_EXPORT
on_button_remove_snippet_clicked(GtkWidget * widget,void * user)261 void on_button_remove_snippet_clicked (GtkWidget* widget, void* user) {
262 GuSnippetsGui* s = gui->snippetsgui;
263 gchar* path_str = NULL;
264 GtkTreeIter iter;
265 GtkTreeModel* model = NULL;
266 GtkTreePath* path = NULL;
267 GtkTreeSelection* selection = NULL;
268
269 model = gtk_tree_view_get_model (s->snippets_tree_view);
270 selection = gtk_tree_view_get_selection (s->snippets_tree_view);
271
272 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
273 gchar* accel = NULL;
274 gchar* config = NULL;
275 gchar* key = NULL;
276 gchar* name = NULL;
277 slist* target = NULL;
278
279 gtk_tree_model_get (model, &iter, 0, &name, 1, &key, 2, &accel, -1);
280 path = gtk_tree_model_get_path (model, &iter);
281 path_str = gtk_tree_path_to_string (path);
282
283 /* Because this function is also called by on_snippet_renderer_edited
284 * where the snippet to be remove isn't inserted into slist, we only
285 * remove if the snippets is already in the slist */
286 if (widget) {
287 config = g_strdup_printf ("%s,%s,%s", key, accel, name);
288 target = slist_find (gummi->snippets->head, config, FALSE, FALSE);
289 slist_remove (gummi->snippets->head, target);
290 }
291 /* Disconnect accelerator */
292 if (key) snippets_accel_disconnect (gummi->snippets, key);
293
294 /* Activate previous item if the removed snippet is not the last one */
295 if (gtk_list_store_remove (s->list_snippets, &iter)) {
296 snippetsgui_move_cursor_to_row (gui->snippetsgui, atoi (path_str));
297 } else if (gtk_tree_model_get_iter_first (model, &iter)) {
298 snippetsgui_move_cursor_to_row(gui->snippetsgui, atoi(path_str) -1);
299 } else {
300 gtk_text_buffer_set_text (GTK_TEXT_BUFFER (s->buffer), "", -1);
301 gtk_entry_set_text (s->tab_trigger_entry, "");
302 gtk_entry_set_text (s->accelerator_entry, "");
303 }
304 g_free (accel);
305 g_free (config);
306 g_free (key);
307 g_free (name);
308 g_free (path_str);
309 }
310 }
311
312 G_MODULE_EXPORT
on_tab_trigger_entry_key_release_event(GtkEntry * entry,void * user)313 gboolean on_tab_trigger_entry_key_release_event (GtkEntry* entry, void* user) {
314 GuSnippetsGui* s = gui->snippetsgui;
315 const gchar* new_key = gtk_entry_get_text (entry);
316 gchar* search_key = NULL;
317 slist* index = NULL;
318
319 /* Check dumplicate key */
320 search_key = g_strdup_printf ("%s,", new_key);
321 index = slist_find (gummi->snippets->head, search_key, TRUE, FALSE);
322
323 if (index && index != s->current) {
324 gtk_entry_set_text (entry, "");
325 slog (L_G_ERROR, _("Duplicate activation tab trigger dectected! Please "
326 "choose another one.\n"));
327 } else {
328 snippetsgui_update_snippet (gummi->snippets);
329 }
330 g_free (search_key);
331
332 return FALSE;
333 }
334
335 G_MODULE_EXPORT
on_accelerator_entry_focus_in_event(GtkWidget * widget,void * user)336 void on_accelerator_entry_focus_in_event (GtkWidget* widget, void* user) {
337 GuSnippetsGui* s = gui->snippetsgui;
338 if (!strlen (gtk_entry_get_text (s->accelerator_entry)))
339 gtk_entry_set_text (s->accelerator_entry, _("Type a new shortcut"));
340 else
341 gtk_entry_set_text (s->accelerator_entry,
342 _("Type a new shortcut, or press Backspace to clear"));
343 }
344
345 G_MODULE_EXPORT
on_accelerator_entry_focus_out_event(GtkWidget * widget,void * user)346 void on_accelerator_entry_focus_out_event (GtkWidget* widget, void* user) {
347 GuSnippetsGui* s = gui->snippetsgui;
348 gchar** configs = NULL;
349 configs = g_strsplit (s->current->first, ",", 0);
350 gtk_entry_set_text (s->accelerator_entry, configs[1]);
351 g_strfreev (configs);
352 }
353
354 G_MODULE_EXPORT
on_accelerator_entry_key_press_event(GtkWidget * widget,GdkEventKey * event,void * user)355 gboolean on_accelerator_entry_key_press_event (GtkWidget* widget,
356 GdkEventKey* event, void* user) {
357 GuSnippetsGui* s = gui->snippetsgui;
358 gchar* new_accel = NULL;
359
360 if (event->keyval == GDK_KEY_Escape) {
361 /* Reset */
362 gtk_entry_set_text (s->accelerator_entry, "");
363 snippetsgui_update_snippet (gummi->snippets);
364 gtk_widget_grab_focus (GTK_WIDGET (s->snippets_tree_view));
365 } else if (event->keyval == GDK_KEY_BackSpace
366 || event->keyval == GDK_KEY_Delete) {
367 /* Remove accelerator */
368 gtk_entry_set_text (s->accelerator_entry, "");
369 snippetsgui_update_snippet (gummi->snippets);
370 gtk_widget_grab_focus (GTK_WIDGET (s->snippets_tree_view));
371 } else if (gtk_accelerator_valid (event->keyval, event->state)) {
372 /* New accelerator */
373 new_accel = gtk_accelerator_name (event->keyval,
374 gtk_accelerator_get_default_mod_mask () & event->state);
375 gtk_entry_set_text (s->accelerator_entry, new_accel);
376 snippetsgui_update_snippet (gummi->snippets);
377 g_free (new_accel);
378 gtk_widget_grab_focus (GTK_WIDGET (s->snippets_tree_view));
379 }
380 return TRUE;
381 }
382
383 G_MODULE_EXPORT
on_snippets_tree_view_cursor_changed(GtkTreeView * view,void * user)384 void on_snippets_tree_view_cursor_changed (GtkTreeView* view, void* user) {
385 GuSnippetsGui* s = gui->snippetsgui;
386 gchar* accel = NULL;
387 gchar* config = NULL;
388 gchar* key = NULL;
389 gchar* name = NULL;
390 gchar* snippet = NULL;
391 GtkTreeIter iter;
392 GtkTreeModel* model = NULL;
393 GtkTreeSelection* selection = NULL;
394
395 model = GTK_TREE_MODEL (gtk_tree_view_get_model (view));
396 selection = gtk_tree_view_get_selection (view);
397
398 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
399 gtk_tree_model_get (model, &iter, 0, &name, 1, &key, 2, &accel, -1);
400
401 /* New entry */
402 if (!name && !key && !accel)
403 return;
404
405 /* Record current activated snippet */
406 config = g_strdup_printf ("%s,%s,%s", key, accel, name);
407 s->current = slist_find (gummi->snippets->head, config, FALSE, FALSE);
408
409 snippet = snippets_get_value (gummi->snippets, key);
410
411 gtk_text_buffer_set_text (GTK_TEXT_BUFFER (s->buffer), snippet, -1);
412 gtk_entry_set_text (s->tab_trigger_entry, key);
413 gtk_entry_set_text (s->accelerator_entry, accel);
414
415 g_free (config);
416 g_free (name);
417 g_free (key);
418 g_free (accel);
419 }
420 }
421
422 G_MODULE_EXPORT
on_snippet_renderer_edited(GtkCellRendererText * renderer,gchar * path,gchar * name,void * user)423 void on_snippet_renderer_edited (GtkCellRendererText* renderer, gchar *path,
424 gchar* name, void* user) {
425 GuSnippetsGui* s = gui->snippetsgui;
426 GtkTreeIter iter;
427 GtkTreeModel* model = NULL;
428 GtkTreeSelection* selection = NULL;
429
430 g_object_set (renderer, "editable", FALSE, NULL);
431 model = gtk_tree_view_get_model (s->snippets_tree_view);
432 selection = gtk_tree_view_get_selection (s->snippets_tree_view);
433
434 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
435 gtk_list_store_set (s->list_snippets, &iter, 0, name, 1, "", 2, "", -1);
436 if (strlen (name)) {
437 slist* node = g_new0 (slist, 1);
438 node->first = g_strdup_printf (",,%s", name);
439 node->second = g_strdup ("");
440 gummi->snippets->head = slist_append (gummi->snippets->head, node);
441 s->current = node;
442 on_snippets_tree_view_cursor_changed (s->snippets_tree_view, NULL);
443 } else {
444 on_button_remove_snippet_clicked (NULL, NULL);
445 }
446 }
447 gtk_widget_set_sensitive (GTK_WIDGET (s->button_new), TRUE);
448 gtk_widget_set_sensitive (GTK_WIDGET (s->button_remove), TRUE);
449 }
450
451 G_MODULE_EXPORT
on_snippet_renderer_editing_canceled(GtkCellRenderer * rend,void * user)452 void on_snippet_renderer_editing_canceled (GtkCellRenderer* rend, void* user) {
453 on_snippet_renderer_edited (GTK_CELL_RENDERER_TEXT (rend), "", "", NULL);
454 }
455
on_snippet_source_buffer_key_release(GtkWidget * widget,void * user)456 gboolean on_snippet_source_buffer_key_release (GtkWidget* widget, void* user) {
457 GuSnippetsGui* s = gui->snippetsgui;
458 GtkTextIter start, end;
459
460 gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (gui->snippetsgui->buffer),
461 &start, &end);
462 gchar* text = gtk_text_iter_get_text (&start, &end);
463 g_free (s->current->second);
464 s->current->second = g_strdup (text);
465 g_free (text);
466 return FALSE;
467 }
468