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