Use GdkRectangle for LayoutOptions::log_window
[geeqie.git] / src / bar-comment.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "bar-comment.h"
24
25 #include "bar.h"
26 #include "metadata.h"
27 #include "filedata.h"
28 #include "ui-menu.h"
29 #include "ui-misc.h"
30 #include "rcfile.h"
31 #include "layout.h"
32
33 #ifdef HAVE_SPELL
34 #include <gspell/gspell.h>
35 #endif
36
37 static void bar_pane_comment_changed(GtkTextBuffer *buffer, gpointer data);
38
39 /*
40  *-------------------------------------------------------------------
41  * keyword / comment utils
42  *-------------------------------------------------------------------
43  */
44
45
46
47 struct PaneCommentData
48 {
49         PaneData pane;
50         GtkWidget *widget;
51         GtkWidget *comment_view;
52         FileData *fd;
53         gchar *key;
54         gint height;
55 #ifdef HAVE_SPELL
56         GspellTextView *gspell_view;
57 #endif
58 };
59
60
61 static void bar_pane_comment_write(PaneCommentData *pcd)
62 {
63         gchar *comment;
64
65         if (!pcd->fd) return;
66
67         comment = text_widget_text_pull(pcd->comment_view);
68
69         metadata_write_string(pcd->fd, pcd->key, comment);
70         g_free(comment);
71 }
72
73
74 static void bar_pane_comment_update(PaneCommentData *pcd)
75 {
76         gchar *comment = nullptr;
77         gchar *orig_comment = nullptr;
78         const gchar *comment_not_null;
79         gshort rating;
80         GtkTextBuffer *comment_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pcd->comment_view));
81
82         orig_comment = text_widget_text_pull(pcd->comment_view);
83         if (g_strcmp0(pcd->key, "Xmp.xmp.Rating") == 0)
84                 {
85                 rating = metadata_read_int(pcd->fd, pcd->key, 0);
86                 comment = g_strdup_printf("%d", rating);
87                 }
88         else
89                 {
90                 comment = metadata_read_string(pcd->fd, pcd->key, METADATA_PLAIN);
91                 }
92         comment_not_null = (comment) ? comment : "";
93
94         if (strcmp(orig_comment, comment_not_null) != 0)
95                 {
96                 g_signal_handlers_block_by_func(comment_buffer, (gpointer)bar_pane_comment_changed, pcd);
97                 gtk_text_buffer_set_text(comment_buffer, comment_not_null, -1);
98                 g_signal_handlers_unblock_by_func(comment_buffer, (gpointer)bar_pane_comment_changed, pcd);
99                 }
100         g_free(comment);
101         g_free(orig_comment);
102
103         gtk_widget_set_sensitive(pcd->comment_view, (pcd->fd != nullptr));
104 }
105
106 static void bar_pane_comment_set_selection(PaneCommentData *pcd, gboolean append)
107 {
108         GList *list = nullptr;
109         GList *work;
110         gchar *comment = nullptr;
111
112         comment = text_widget_text_pull(pcd->comment_view);
113
114         list = layout_selection_list(pcd->pane.lw);
115         list = file_data_process_groups_in_selection(list, FALSE, nullptr);
116
117         work = list;
118         while (work)
119                 {
120                 auto fd = static_cast<FileData *>(work->data);
121                 work = work->next;
122                 if (fd == pcd->fd) continue;
123
124                 if (append)
125                         {
126                         metadata_append_string(fd, pcd->key, comment);
127                         }
128                 else
129                         {
130                         metadata_write_string(fd, pcd->key, comment);
131                         }
132                 }
133
134         filelist_free(list);
135         g_free(comment);
136 }
137
138 static void bar_pane_comment_sel_add_cb(GtkWidget *, gpointer data)
139 {
140         auto pcd = static_cast<PaneCommentData *>(data);
141
142         bar_pane_comment_set_selection(pcd, TRUE);
143 }
144
145 static void bar_pane_comment_sel_replace_cb(GtkWidget *, gpointer data)
146 {
147         auto pcd = static_cast<PaneCommentData *>(data);
148
149         bar_pane_comment_set_selection(pcd, FALSE);
150 }
151
152
153 static void bar_pane_comment_set_fd(GtkWidget *bar, FileData *fd)
154 {
155         PaneCommentData *pcd;
156
157         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(bar), "pane_data"));
158         if (!pcd) return;
159
160         file_data_unref(pcd->fd);
161         pcd->fd = file_data_ref(fd);
162
163         bar_pane_comment_update(pcd);
164 }
165
166 static gint bar_pane_comment_event(GtkWidget *bar, GdkEvent *event)
167 {
168         PaneCommentData *pcd;
169
170         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(bar), "pane_data"));
171         if (!pcd) return FALSE;
172
173         if (gtk_widget_has_focus(pcd->comment_view)) return gtk_widget_event(pcd->comment_view, event);
174
175         return FALSE;
176 }
177
178 static void bar_pane_comment_write_config(GtkWidget *pane, GString *outstr, gint indent)
179 {
180         PaneCommentData *pcd;
181         gint w, h;
182
183         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
184         if (!pcd) return;
185
186         gtk_widget_get_size_request(GTK_WIDGET(pane), &w, &h);
187
188         if (!g_strcmp0(pcd->pane.id, "title"))
189                 {
190                 pcd->height = h;
191                 }
192         if (!g_strcmp0(pcd->pane.id, "comment"))
193                 {
194                 pcd->height = h;
195                 }
196         if (!g_strcmp0(pcd->pane.id, "rating"))
197                 {
198                 pcd->height = h;
199                 }
200         if (!g_strcmp0(pcd->pane.id, "headline"))
201                 {
202                 pcd->height = h;
203                 }
204
205         WRITE_NL(); WRITE_STRING("<pane_comment ");
206         write_char_option(outstr, indent, "id", pcd->pane.id);
207         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(pcd->pane.title)));
208         WRITE_BOOL(pcd->pane, expanded);
209         WRITE_CHAR(*pcd, key);
210         WRITE_INT(*pcd, height);
211         WRITE_STRING("/>");
212 }
213
214 static void bar_pane_comment_notify_cb(FileData *fd, NotifyType type, gpointer data)
215 {
216         auto pcd = static_cast<PaneCommentData *>(data);
217         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == pcd->fd)
218                 {
219                 DEBUG_1("Notify pane_comment: %s %04x", fd->path, type);
220
221                 bar_pane_comment_update(pcd);
222                 }
223 }
224
225 static void bar_pane_comment_changed(GtkTextBuffer *, gpointer data)
226 {
227         auto pcd = static_cast<PaneCommentData *>(data);
228
229         bar_pane_comment_write(pcd);
230 }
231
232
233 static void bar_pane_comment_populate_popup(GtkTextView *, GtkMenu *menu, gpointer data)
234 {
235         auto pcd = static_cast<PaneCommentData *>(data);
236
237         menu_item_add_divider(GTK_WIDGET(menu));
238         menu_item_add_icon(GTK_WIDGET(menu), _("Add text to selected files"), GQ_ICON_ADD, G_CALLBACK(bar_pane_comment_sel_add_cb), pcd);
239         menu_item_add_icon(GTK_WIDGET(menu), _("Replace existing text in selected files"), GQ_ICON_REPLACE, G_CALLBACK(bar_pane_comment_sel_replace_cb), data);
240 }
241
242 static void bar_pane_comment_destroy(GtkWidget *, gpointer data)
243 {
244         auto pcd = static_cast<PaneCommentData *>(data);
245
246         file_data_unregister_notify_func(bar_pane_comment_notify_cb, pcd);
247 #ifdef HAVE_SPELL
248         g_object_unref(pcd->gspell_view);
249 #endif
250         file_data_unref(pcd->fd);
251         g_free(pcd->key);
252
253         g_free(pcd->pane.id);
254
255         g_free(pcd);
256 }
257
258
259 static GtkWidget *bar_pane_comment_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height)
260 {
261         PaneCommentData *pcd;
262         GtkWidget *scrolled;
263         GtkTextBuffer *buffer;
264
265         pcd = g_new0(PaneCommentData, 1);
266
267         pcd->pane.pane_set_fd = bar_pane_comment_set_fd;
268         pcd->pane.pane_event = bar_pane_comment_event;
269         pcd->pane.pane_write_config = bar_pane_comment_write_config;
270         pcd->pane.title = bar_pane_expander_title(title);
271         pcd->pane.id = g_strdup(id);
272         pcd->pane.type = PANE_COMMENT;
273 #ifdef HAVE_SPELL
274         pcd->gspell_view = nullptr;
275 #endif
276         pcd->pane.expanded = expanded;
277
278         pcd->key = g_strdup(key);
279         pcd->height = height;
280
281         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
282
283         pcd->widget = scrolled;
284         g_object_set_data(G_OBJECT(pcd->widget), "pane_data", pcd);
285         g_signal_connect(G_OBJECT(pcd->widget), "destroy",
286                          G_CALLBACK(bar_pane_comment_destroy), pcd);
287
288         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
289         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
290                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
291
292         gtk_widget_set_size_request(pcd->widget, -1, height);
293         gtk_widget_show(scrolled);
294
295         pcd->comment_view = gtk_text_view_new();
296         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(pcd->comment_view), GTK_WRAP_WORD);
297         gq_gtk_container_add(GTK_WIDGET(scrolled), pcd->comment_view);
298         g_signal_connect(G_OBJECT(pcd->comment_view), "populate-popup",
299                          G_CALLBACK(bar_pane_comment_populate_popup), pcd);
300         gtk_widget_show(pcd->comment_view);
301
302 #ifdef HAVE_SPELL
303         if (g_strcmp0(key, "Xmp.xmp.Rating") != 0)
304                 {
305                 if (options->metadata.check_spelling)
306                         {
307                         pcd->gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(pcd->comment_view));
308                         gspell_text_view_basic_setup(pcd->gspell_view);
309                         }
310         }
311 #endif
312
313         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pcd->comment_view));
314         g_signal_connect(G_OBJECT(buffer), "changed",
315                          G_CALLBACK(bar_pane_comment_changed), pcd);
316
317
318         file_data_register_notify_func(bar_pane_comment_notify_cb, pcd, NOTIFY_PRIORITY_LOW);
319
320         return pcd->widget;
321 }
322
323 GtkWidget *bar_pane_comment_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
324 {
325         gchar *title = nullptr;
326         gchar *key = g_strdup(COMMENT_KEY);
327         gboolean expanded = TRUE;
328         gint height = 50;
329         gchar *id = g_strdup("comment");
330         GtkWidget *ret;
331
332         while (*attribute_names)
333                 {
334                 const gchar *option = *attribute_names++;
335                 const gchar *value = *attribute_values++;
336
337                 if (READ_CHAR_FULL("title", title)) continue;
338                 if (READ_CHAR_FULL("key", key)) continue;
339                 if (READ_BOOL_FULL("expanded", expanded)) continue;
340                 if (READ_INT_FULL("height", height)) continue;
341                 if (READ_CHAR_FULL("id", id)) continue;
342
343
344                 log_printf("unknown attribute %s = %s\n", option, value);
345                 }
346
347         if (!g_strcmp0(id, "title"))
348                 {
349                 options->info_title.height = height;
350                 }
351         if (!g_strcmp0(id, "comment"))
352                 {
353                 options->info_comment.height = height;
354                 }
355         if (!g_strcmp0(id, "rating"))
356                 {
357                 options->info_rating.height = height;
358                 }
359         if (!g_strcmp0(id, "headline"))
360                 {
361                 options->info_headline.height = height;
362                 }
363
364         bar_pane_translate_title(PANE_COMMENT, id, &title);
365         ret = bar_pane_comment_new(id, title, key, expanded, height);
366         g_free(title);
367         g_free(key);
368         g_free(id);
369         return ret;
370 }
371
372 void bar_pane_comment_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
373 {
374         PaneCommentData *pcd;
375
376         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
377         if (!pcd) return;
378
379         gchar *title = nullptr;
380
381         while (*attribute_names)
382                 {
383                 const gchar *option = *attribute_names++;
384                 const gchar *value = *attribute_values++;
385
386                 if (READ_CHAR_FULL("title", title)) continue;
387                 if (READ_CHAR_FULL("key", pcd->key)) continue;
388                 if (READ_BOOL_FULL("expanded", pcd->pane.expanded)) continue;
389                 if (READ_INT_FULL("height", pcd->height)) continue;
390                 if (READ_CHAR_FULL("id", pcd->pane.id)) continue;
391
392
393                 log_printf("unknown attribute %s = %s\n", option, value);
394                 }
395
396         if (title)
397                 {
398                 bar_pane_translate_title(PANE_COMMENT, pcd->pane.id, &title);
399                 gtk_label_set_text(GTK_LABEL(pcd->pane.title), title);
400                 g_free(title);
401                 }
402         gtk_widget_set_size_request(pcd->widget, -1, pcd->height);
403         bar_update_expander(pane);
404         bar_pane_comment_update(pcd);
405 }
406
407 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */