1 /*
2  * Copyright (C) 2008 Igalia
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors:
18  *   Pablo Sanxiao <psanxiao@gmail.com>
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include "gtr-application.h"
26 #include "gtr-po.h"
27 #include "gtr-translation-memory.h"
28 #include "gtr-translation-memory-ui.h"
29 #include "gtr-tab.h"
30 #include "gtr-utils.h"
31 #include "gtr-window.h"
32 #include "gtr-debug.h"
33 
34 #include <string.h>
35 #include <glib.h>
36 #include <glib/gi18n.h>
37 #include <glib-object.h>
38 #include <gtk/gtk.h>
39 #include <gdk/gdkkeysyms.h>
40 
41 #define MAX_ELEMENTS 9
42 
43 typedef struct
44 {
45   GtrTranslationMemory *translation_memory;
46   GtkWidget *tree_view;
47   GtrTab *tab;
48 
49   gchar **tm_list;
50   gint *tm_list_id;
51 
52   GtkWidget *popup_menu;
53   GtrMsg *msg;
54 } GtrTranslationMemoryUiPrivate;
55 
56 
57 G_DEFINE_TYPE_WITH_PRIVATE (GtrTranslationMemoryUi, gtr_translation_memory_ui, GTK_TYPE_SCROLLED_WINDOW)
58 
59 enum
60 {
61   SHORTCUT_COLUMN,
62   LEVEL_COLUMN,
63   STRING_COLUMN,
64   N_COLUMNS
65 };
66 
67 static void
68 tree_view_size_cb (GtkWidget * widget,
69                    GtkAllocation * allocation,
70                    gpointer user_data);
71 
72 static void
choose_translation(GtrTranslationMemoryUi * tm_ui,const gchar * translation)73 choose_translation (GtrTranslationMemoryUi *tm_ui, const gchar *translation)
74 {
75   GtrView *view;
76   GtkTextBuffer *buffer;
77   GtrPo *po;
78   GList *current_msg = NULL;
79   GtrMsg *msg;
80   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
81 
82   view = gtr_tab_get_active_view (priv->tab);
83   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
84 
85   po = gtr_tab_get_po (priv->tab);
86   current_msg = gtr_po_get_current_message (po);
87 
88   msg = GTR_MSG (current_msg->data);
89 
90   gtr_msg_set_msgstr (msg, translation);
91 
92   gtk_text_buffer_begin_user_action (buffer);
93   gtk_text_buffer_set_text (buffer, translation, -1);
94   gtk_text_buffer_end_user_action (buffer);
95 
96   gtr_po_set_state (po, GTR_PO_STATE_MODIFIED);
97 }
98 
99 static void
free_match(gpointer data)100 free_match (gpointer data)
101 {
102   GtrTranslationMemoryMatch *match = (GtrTranslationMemoryMatch *) data;
103 
104   g_free (match->match);
105   g_slice_free (GtrTranslationMemoryMatch, match);
106 }
107 
108 static void
showed_message_cb(GtrTab * tab,GtrMsg * msg,GtrTranslationMemoryUi * tm_ui)109 showed_message_cb (GtrTab *tab, GtrMsg *msg, GtrTranslationMemoryUi *tm_ui)
110 {
111   GtkListStore *model;
112   GtkTreeIter iter;
113   GtkTreeViewColumn *level_column;
114   const gchar *msgid;
115   gint i;
116   GList *tm_list = NULL;
117   GList *l = NULL;
118   GList *renderers_list = NULL;
119   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
120 
121   model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)));
122 
123   g_signal_connect (priv->tree_view,
124                     "size_allocate",
125                     G_CALLBACK (tree_view_size_cb), priv->tree_view);
126 
127   if (priv->msg)
128     g_object_unref (priv->msg);
129   priv->msg = g_object_ref (msg);
130 
131   msgid = gtr_msg_get_msgid (msg);
132 
133   tm_list = gtr_translation_memory_lookup (priv->translation_memory, msgid);
134   g_strfreev (priv->tm_list);
135 
136   gtk_list_store_clear (model);
137   priv->tm_list = g_new (gchar *, MAX_ELEMENTS + 1);
138   priv->tm_list_id = g_new (gint, MAX_ELEMENTS + 1);
139 
140   i = 0;
141   for (l = tm_list; l && i < MAX_ELEMENTS; l = l->next)
142     {
143       GtrTranslationMemoryMatch *match = (GtrTranslationMemoryMatch *) l->data;
144 
145       priv->tm_list_id[i] = match->id;
146       priv->tm_list[i] = g_strdup (match->match);
147       level_column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->tree_view), 0);
148       renderers_list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (level_column));
149 
150       g_object_set (renderers_list->data,
151                     "accel-mods", GDK_CONTROL_MASK, NULL);
152       g_list_free (renderers_list);
153 
154       gtk_list_store_append (model, &iter);
155       gtk_list_store_set (model, &iter,
156                           SHORTCUT_COLUMN, GDK_KEY_1 + i,
157                           STRING_COLUMN, match->match,
158                           LEVEL_COLUMN, match->level,
159                           -1);
160       i++;
161     }
162 
163   /* Ensure last element is NULL */
164   priv->tm_list[i] = NULL;
165 
166   g_list_free_full (tm_list, free_match);
167 }
168 
169 static void
tree_view_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,GtrTranslationMemoryUi * tm_ui)170 tree_view_row_activated (GtkTreeView *tree_view,
171                          GtkTreePath *path,
172                          GtkTreeViewColumn *column,
173                          GtrTranslationMemoryUi *tm_ui)
174 {
175   GtkTreeModel *model;
176   GtkTreeIter iter;
177   gchar *translation;
178 
179   model = gtk_tree_view_get_model (tree_view);
180 
181   if (!gtk_tree_model_get_iter (model, &iter, path))
182     return;
183 
184   gtk_tree_model_get (model, &iter,
185                       STRING_COLUMN, &translation,
186                       -1);
187 
188   choose_translation (tm_ui, translation);
189 
190   g_free (translation);
191 }
192 
193 static void
popup_menu_translation_activate(GtkMenuItem * menuitem,GtrTranslationMemoryUi * tm_ui)194 popup_menu_translation_activate (GtkMenuItem *menuitem,
195                                  GtrTranslationMemoryUi *tm_ui)
196 {
197   GtkTreeSelection *selection;
198   GtkTreeModel *model;
199   GtkTreeIter iter;
200   gchar *translation;
201   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
202 
203   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
204   if (!selection || !gtk_tree_selection_get_selected (selection, &model, &iter))
205     return;
206 
207   gtk_tree_model_get (model, &iter,
208                       STRING_COLUMN, &translation,
209                       -1);
210 
211   choose_translation (tm_ui, translation);
212 
213   g_free (translation);
214 }
215 
216 static void
popup_menu_remove_from_memory(GtkMenuItem * menuitem,GtrTranslationMemoryUi * tm_ui)217 popup_menu_remove_from_memory (GtkMenuItem *menuitem,
218                                GtrTranslationMemoryUi *tm_ui)
219 {
220   GtkTreeSelection *selection;
221   GtkTreeModel *model;
222   GtkTreeIter iter;
223   gint i;
224   gchar *translation;
225   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
226 
227   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
228   if (!selection || !gtk_tree_selection_get_selected (selection, &model, &iter))
229     return;
230 
231   gtk_tree_model_get (model, &iter,
232                       STRING_COLUMN, &translation,
233                       -1);
234 
235   for (i = 0; priv->tm_list[i]; i++)
236     if (!strcmp (priv->tm_list[i], translation))
237       break;
238 
239   gtr_translation_memory_remove (priv->translation_memory, priv->tm_list_id[i]);
240 
241   g_free (translation);
242 
243   /* update list */
244   showed_message_cb (priv->tab, priv->msg, tm_ui);
245 }
246 
247 static GtkWidget *
create_tree_popup_menu(GtrTranslationMemoryUi * self)248 create_tree_popup_menu (GtrTranslationMemoryUi *self)
249 {
250   GtkWidget *menu;
251   GtkWidget *item;
252 
253   menu = gtk_menu_new ();
254 
255   item = gtk_menu_item_new_with_mnemonic (_("_Use this translation"));
256   g_signal_connect (item, "activate",
257                     G_CALLBACK (popup_menu_translation_activate), self);
258   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
259 
260   item = gtk_menu_item_new_with_mnemonic (_("_Remove"));
261   g_signal_connect (item, "activate",
262                     G_CALLBACK (popup_menu_remove_from_memory), self);
263   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
264   gtk_widget_set_sensitive (item, TRUE);
265 
266   gtk_widget_show_all (menu);
267 
268   return menu;
269 }
270 
271 static void
tree_popup_menu_detach(GtrTranslationMemoryUi * self,GtkMenu * menu)272 tree_popup_menu_detach (GtrTranslationMemoryUi *self, GtkMenu * menu)
273 {
274   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (self);
275   priv->popup_menu = NULL;
276 }
277 
278 static void
gtr_translation_memory_ui_show_menu(GtrTranslationMemoryUi * self,GdkEventButton * event)279 gtr_translation_memory_ui_show_menu (GtrTranslationMemoryUi *self,
280                                      GdkEventButton * event)
281 {
282   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (self);
283   if (priv->popup_menu)
284     gtk_widget_destroy (priv->popup_menu);
285 
286   priv->popup_menu = create_tree_popup_menu (self);
287 
288   gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
289                              GTK_WIDGET (self),
290                              (GtkMenuDetachFunc) tree_popup_menu_detach);
291 
292   if (event != NULL)
293     gtk_menu_popup_at_pointer (GTK_MENU (priv->popup_menu), (GdkEvent*)event);
294   else
295     {
296       GdkEvent *ev = gtk_get_current_event ();
297       gtk_menu_popup_at_pointer (GTK_MENU (priv->popup_menu), ev);
298     }
299 }
300 
301 static void
tree_view_size_cb(GtkWidget * widget,GtkAllocation * allocation,gpointer user_data)302 tree_view_size_cb (GtkWidget * widget,
303                    GtkAllocation * allocation,
304                    gpointer user_data)
305 {
306   GtkTreeView *treeview;
307   GtkTreeViewColumn *column;
308   GList *renderers_list = NULL;
309   gint size;
310 
311   treeview = GTK_TREE_VIEW (user_data);
312 
313   column = gtk_tree_view_get_column (treeview, 2);
314   renderers_list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
315 
316   size = gtk_tree_view_column_get_width (column);
317 
318   g_object_set (renderers_list->data, "wrap-width", MAX(1, size - 10), NULL);
319 
320   g_list_free (renderers_list);
321 }
322 
323 static gboolean
tree_view_button_press_event(GtkTreeView * tree,GdkEventButton * event,GtrTranslationMemoryUi * tm_ui)324 tree_view_button_press_event (GtkTreeView *tree,
325                               GdkEventButton *event,
326                               GtrTranslationMemoryUi *tm_ui)
327 {
328   GtkTreePath *path;
329 
330   if (GDK_BUTTON_PRESS != event->type || 3 != event->button)
331     return FALSE;
332 
333   if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y,
334                                       &path, NULL, NULL, NULL))
335     return FALSE;
336 
337   gtk_widget_grab_focus (GTK_WIDGET (tree));
338   gtk_tree_view_set_cursor (tree, path, NULL, FALSE);
339 
340   gtr_translation_memory_ui_show_menu (tm_ui, event);
341   return TRUE;
342 }
343 
344 static gboolean
tree_view_popup_menu(GtkTreeView * tree,GtrTranslationMemoryUi * tm_ui)345 tree_view_popup_menu (GtkTreeView *tree, GtrTranslationMemoryUi *tm_ui)
346 {
347   gtr_translation_memory_ui_show_menu (tm_ui, NULL);
348   return TRUE;
349 }
350 
351 static void
gtr_translation_memory_ui_init(GtrTranslationMemoryUi * tm_ui)352 gtr_translation_memory_ui_init (GtrTranslationMemoryUi * tm_ui)
353 {
354   GtkListStore *model;
355   GtkCellRenderer *level_renderer, *string_renderer, *shortcut_renderer;
356   GtkTreeViewColumn *shortcut, *string, *level;
357   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
358 
359   priv->tm_list = NULL;
360   priv->popup_menu = NULL;
361   priv->msg = NULL;
362 
363   priv->tree_view = gtk_tree_view_new ();
364   gtk_widget_show (priv->tree_view);
365 
366   model = gtk_list_store_new (N_COLUMNS, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
367   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view),
368                            GTK_TREE_MODEL (model));
369 
370   shortcut_renderer = gtk_cell_renderer_accel_new ();
371   shortcut = gtk_tree_view_column_new_with_attributes (_("Shortcut"),
372                                                        shortcut_renderer,
373                                                        "accel-key",
374                                                        SHORTCUT_COLUMN, NULL);
375   g_object_set (shortcut_renderer, "width", 80, NULL);
376 
377   gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), shortcut);
378 
379   level_renderer = gtk_cell_renderer_progress_new ();
380   level = gtk_tree_view_column_new_with_attributes (_("Level"),
381                                                     level_renderer,
382                                                     "value", LEVEL_COLUMN,
383                                                     NULL);
384   g_object_set (level_renderer, "width", 80, NULL);
385 
386   gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), level);
387 
388   string_renderer = gtk_cell_renderer_text_new ();
389   string = gtk_tree_view_column_new_with_attributes (_("String"),
390                                                      string_renderer,
391                                                      "text", STRING_COLUMN,
392                                                      NULL);
393   gtk_tree_view_column_set_sizing (string, GTK_TREE_VIEW_COLUMN_FIXED);
394 
395   g_object_set (string_renderer,
396                 "ypad", 0,
397                 "xpad", 5,
398                 "yalign", 0.0,
399                 "wrap-mode", PANGO_WRAP_WORD_CHAR,
400                 NULL);
401 
402   gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), string);
403 
404   g_signal_connect (priv->tree_view, "button-press-event",
405                     G_CALLBACK (tree_view_button_press_event), tm_ui);
406 
407   g_signal_connect (priv->tree_view, "popup-menu",
408                     G_CALLBACK (tree_view_popup_menu), tm_ui);
409 
410   g_signal_connect (priv->tree_view, "row-activated",
411                     G_CALLBACK (tree_view_row_activated), tm_ui);
412 }
413 
414 static void
gtr_translation_memory_ui_dispose(GObject * object)415 gtr_translation_memory_ui_dispose (GObject * object)
416 {
417   GtrTranslationMemoryUi *tm_ui = GTR_TRANSLATION_MEMORY_UI (object);
418   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
419 
420   DEBUG_PRINT ("Dispose translation memory ui");
421 
422   g_clear_object (&priv->msg);
423 
424   G_OBJECT_CLASS (gtr_translation_memory_ui_parent_class)->dispose (object);
425 }
426 
427 static void
gtr_translation_memory_ui_finalize(GObject * object)428 gtr_translation_memory_ui_finalize (GObject * object)
429 {
430   GtrTranslationMemoryUi *tm_ui = GTR_TRANSLATION_MEMORY_UI (object);
431   GtrTranslationMemoryUiPrivate *priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
432 
433   DEBUG_PRINT ("Finalize translation memory ui");
434 
435   g_strfreev (priv->tm_list);
436 
437   G_OBJECT_CLASS (gtr_translation_memory_ui_parent_class)->finalize (object);
438 }
439 
440 static void
gtr_translation_memory_ui_class_init(GtrTranslationMemoryUiClass * klass)441 gtr_translation_memory_ui_class_init (GtrTranslationMemoryUiClass * klass)
442 {
443   GObjectClass *object_class = G_OBJECT_CLASS (klass);
444 
445   object_class->dispose = gtr_translation_memory_ui_dispose;
446   object_class->finalize = gtr_translation_memory_ui_finalize;
447 }
448 
449 GtkWidget *
gtr_translation_memory_ui_new(GtkWidget * tab,GtrTranslationMemory * translation_memory)450 gtr_translation_memory_ui_new (GtkWidget *tab,
451                                GtrTranslationMemory *translation_memory)
452 {
453   GtrTranslationMemoryUi *tm_ui;
454   GtrTranslationMemoryUiPrivate *priv;
455   tm_ui = g_object_new (GTR_TYPE_TRANSLATION_MEMORY_UI, NULL);
456 
457   priv = gtr_translation_memory_ui_get_instance_private (tm_ui);
458   priv->tab = GTR_TAB (tab);
459   priv->translation_memory = translation_memory;
460 
461   g_signal_connect (tab,
462                     "showed-message", G_CALLBACK (showed_message_cb), tm_ui);
463 
464   /* Scrolledwindow needs to be realized to add a widget */
465   gtk_container_add (GTK_CONTAINER (tm_ui), priv->tree_view);
466 
467   return GTK_WIDGET (tm_ui);
468 }
469