clang-tidy: readability-isolate-declaration
[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;
182         gint h;
183
184         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
185         if (!pcd) return;
186
187         gtk_widget_get_size_request(GTK_WIDGET(pane), &w, &h);
188
189         if (!g_strcmp0(pcd->pane.id, "title"))
190                 {
191                 pcd->height = h;
192                 }
193         if (!g_strcmp0(pcd->pane.id, "comment"))
194                 {
195                 pcd->height = h;
196                 }
197         if (!g_strcmp0(pcd->pane.id, "rating"))
198                 {
199                 pcd->height = h;
200                 }
201         if (!g_strcmp0(pcd->pane.id, "headline"))
202                 {
203                 pcd->height = h;
204                 }
205
206         WRITE_NL(); WRITE_STRING("<pane_comment ");
207         write_char_option(outstr, indent, "id", pcd->pane.id);
208         write_char_option(outstr, indent, "title", gtk_label_get_text(GTK_LABEL(pcd->pane.title)));
209         WRITE_BOOL(pcd->pane, expanded);
210         WRITE_CHAR(*pcd, key);
211         WRITE_INT(*pcd, height);
212         WRITE_STRING("/>");
213 }
214
215 static void bar_pane_comment_notify_cb(FileData *fd, NotifyType type, gpointer data)
216 {
217         auto pcd = static_cast<PaneCommentData *>(data);
218         if ((type & (NOTIFY_REREAD | NOTIFY_CHANGE | NOTIFY_METADATA)) && fd == pcd->fd)
219                 {
220                 DEBUG_1("Notify pane_comment: %s %04x", fd->path, type);
221
222                 bar_pane_comment_update(pcd);
223                 }
224 }
225
226 static void bar_pane_comment_changed(GtkTextBuffer *, gpointer data)
227 {
228         auto pcd = static_cast<PaneCommentData *>(data);
229
230         bar_pane_comment_write(pcd);
231 }
232
233
234 static void bar_pane_comment_populate_popup(GtkTextView *, GtkMenu *menu, gpointer data)
235 {
236         auto pcd = static_cast<PaneCommentData *>(data);
237
238         menu_item_add_divider(GTK_WIDGET(menu));
239         menu_item_add_icon(GTK_WIDGET(menu), _("Add text to selected files"), GQ_ICON_ADD, G_CALLBACK(bar_pane_comment_sel_add_cb), pcd);
240         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);
241 }
242
243 static void bar_pane_comment_destroy(GtkWidget *, gpointer data)
244 {
245         auto pcd = static_cast<PaneCommentData *>(data);
246
247         file_data_unregister_notify_func(bar_pane_comment_notify_cb, pcd);
248 #ifdef HAVE_SPELL
249         g_object_unref(pcd->gspell_view);
250 #endif
251         file_data_unref(pcd->fd);
252         g_free(pcd->key);
253
254         g_free(pcd->pane.id);
255
256         g_free(pcd);
257 }
258
259
260 static GtkWidget *bar_pane_comment_new(const gchar *id, const gchar *title, const gchar *key, gboolean expanded, gint height)
261 {
262         PaneCommentData *pcd;
263         GtkWidget *scrolled;
264         GtkTextBuffer *buffer;
265
266         pcd = g_new0(PaneCommentData, 1);
267
268         pcd->pane.pane_set_fd = bar_pane_comment_set_fd;
269         pcd->pane.pane_event = bar_pane_comment_event;
270         pcd->pane.pane_write_config = bar_pane_comment_write_config;
271         pcd->pane.title = bar_pane_expander_title(title);
272         pcd->pane.id = g_strdup(id);
273         pcd->pane.type = PANE_COMMENT;
274 #ifdef HAVE_SPELL
275         pcd->gspell_view = nullptr;
276 #endif
277         pcd->pane.expanded = expanded;
278
279         pcd->key = g_strdup(key);
280         pcd->height = height;
281
282         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
283
284         pcd->widget = scrolled;
285         g_object_set_data(G_OBJECT(pcd->widget), "pane_data", pcd);
286         g_signal_connect(G_OBJECT(pcd->widget), "destroy",
287                          G_CALLBACK(bar_pane_comment_destroy), pcd);
288
289         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
290         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
291                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
292
293         gtk_widget_set_size_request(pcd->widget, -1, height);
294         gtk_widget_show(scrolled);
295
296         pcd->comment_view = gtk_text_view_new();
297         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(pcd->comment_view), GTK_WRAP_WORD);
298         gq_gtk_container_add(GTK_WIDGET(scrolled), pcd->comment_view);
299         g_signal_connect(G_OBJECT(pcd->comment_view), "populate-popup",
300                          G_CALLBACK(bar_pane_comment_populate_popup), pcd);
301         gtk_widget_show(pcd->comment_view);
302
303 #ifdef HAVE_SPELL
304         if (g_strcmp0(key, "Xmp.xmp.Rating") != 0)
305                 {
306                 if (options->metadata.check_spelling)
307                         {
308                         pcd->gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(pcd->comment_view));
309                         gspell_text_view_basic_setup(pcd->gspell_view);
310                         }
311         }
312 #endif
313
314         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(pcd->comment_view));
315         g_signal_connect(G_OBJECT(buffer), "changed",
316                          G_CALLBACK(bar_pane_comment_changed), pcd);
317
318
319         file_data_register_notify_func(bar_pane_comment_notify_cb, pcd, NOTIFY_PRIORITY_LOW);
320
321         return pcd->widget;
322 }
323
324 GtkWidget *bar_pane_comment_new_from_config(const gchar **attribute_names, const gchar **attribute_values)
325 {
326         gchar *title = nullptr;
327         gchar *key = g_strdup(COMMENT_KEY);
328         gboolean expanded = TRUE;
329         gint height = 50;
330         gchar *id = g_strdup("comment");
331         GtkWidget *ret;
332
333         while (*attribute_names)
334                 {
335                 const gchar *option = *attribute_names++;
336                 const gchar *value = *attribute_values++;
337
338                 if (READ_CHAR_FULL("title", title)) continue;
339                 if (READ_CHAR_FULL("key", key)) continue;
340                 if (READ_BOOL_FULL("expanded", expanded)) continue;
341                 if (READ_INT_FULL("height", height)) continue;
342                 if (READ_CHAR_FULL("id", id)) continue;
343
344
345                 log_printf("unknown attribute %s = %s\n", option, value);
346                 }
347
348         if (!g_strcmp0(id, "title"))
349                 {
350                 options->info_title.height = height;
351                 }
352         if (!g_strcmp0(id, "comment"))
353                 {
354                 options->info_comment.height = height;
355                 }
356         if (!g_strcmp0(id, "rating"))
357                 {
358                 options->info_rating.height = height;
359                 }
360         if (!g_strcmp0(id, "headline"))
361                 {
362                 options->info_headline.height = height;
363                 }
364
365         bar_pane_translate_title(PANE_COMMENT, id, &title);
366         ret = bar_pane_comment_new(id, title, key, expanded, height);
367         g_free(title);
368         g_free(key);
369         g_free(id);
370         return ret;
371 }
372
373 void bar_pane_comment_update_from_config(GtkWidget *pane, const gchar **attribute_names, const gchar **attribute_values)
374 {
375         PaneCommentData *pcd;
376
377         pcd = static_cast<PaneCommentData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
378         if (!pcd) return;
379
380         gchar *title = nullptr;
381
382         while (*attribute_names)
383                 {
384                 const gchar *option = *attribute_names++;
385                 const gchar *value = *attribute_values++;
386
387                 if (READ_CHAR_FULL("title", title)) continue;
388                 if (READ_CHAR_FULL("key", pcd->key)) continue;
389                 if (READ_BOOL_FULL("expanded", pcd->pane.expanded)) continue;
390                 if (READ_INT_FULL("height", pcd->height)) continue;
391                 if (READ_CHAR_FULL("id", pcd->pane.id)) continue;
392
393
394                 log_printf("unknown attribute %s = %s\n", option, value);
395                 }
396
397         if (title)
398                 {
399                 bar_pane_translate_title(PANE_COMMENT, pcd->pane.id, &title);
400                 gtk_label_set_text(GTK_LABEL(pcd->pane.title), title);
401                 g_free(title);
402                 }
403         gtk_widget_set_size_request(pcd->widget, -1, pcd->height);
404         bar_update_expander(pane);
405         bar_pane_comment_update(pcd);
406 }
407
408 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */