Fix #1248: Crash when hiding file list
[geeqie.git] / src / bar.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik
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 "bar.h"
23
24 #include <cstring>
25
26 #include <glib-object.h>
27 #include <pango/pango.h>
28
29 #include <config.h>
30
31 #include "compat.h"
32 #include "debug.h"
33 #include "filedata.h"
34 #include "intl.h"
35 #include "layout.h"
36 #include "main-defines.h"
37 #include "metadata.h"
38 #include "rcfile.h"
39 #include "typedefs.h"
40 #include "ui-menu.h"
41 #include "ui-misc.h"
42
43
44 namespace
45 {
46
47 void remove_child_from_parent(gpointer data)
48 {
49         gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(GTK_WIDGET(data))), GTK_WIDGET(data));
50 }
51
52 }
53
54 struct KnownPanes
55 {
56         PaneType type;
57         const gchar *id;
58         const gchar *title;
59         const gchar *config;
60 };
61
62 static const gchar default_config_histogram[] =
63 "<gq>"
64 "    <layout id = '_current_'>"
65 "        <bar>"
66 "            <pane_histogram id = 'histogram' expanded = 'true' histogram_channel = '4' histogram_mode = '0' />"
67 "        </bar>"
68 "    </layout>"
69 "</gq>";
70
71 static const gchar default_config_title[] =
72 "<gq>"
73 "    <layout id = '_current_'>"
74 "        <bar>"
75 "            <pane_comment id = 'title' expanded = 'true' key = 'Xmp.dc.title' height = '40' />"
76 "        </bar>"
77 "    </layout>"
78 "</gq>";
79
80 static const gchar default_config_headline[] =
81 "<gq>"
82 "    <layout id = '_current_'>"
83 "        <bar>"
84 "            <pane_comment id = 'headline' expanded = 'true' key = 'Xmp.photoshop.Headline'  height = '40' />"
85 "        </bar>"
86 "    </layout>"
87 "</gq>";
88
89 static const gchar default_config_keywords[] =
90 "<gq>"
91 "    <layout id = '_current_'>"
92 "        <bar>"
93 "            <pane_keywords id = 'keywords' expanded = 'true' key = '" KEYWORD_KEY "' />"
94 "        </bar>"
95 "    </layout>"
96 "</gq>";
97
98 static const gchar default_config_comment[] =
99 "<gq>"
100 "    <layout id = '_current_'>"
101 "        <bar>"
102 "            <pane_comment id = 'comment' expanded = 'true' key = '" COMMENT_KEY "' height = '150' />"
103 "        </bar>"
104 "    </layout>"
105 "</gq>";
106 static const gchar default_config_rating[] =
107 "<gq>"
108 "    <layout id = '_current_'>"
109 "        <bar>"
110 "            <pane_rating id = 'rating' expanded = 'true' />"
111 "        </bar>"
112 "    </layout>"
113 "</gq>";
114
115 static const gchar default_config_exif[] =
116 "<gq>"
117 "    <layout id = '_current_'>"
118 "        <bar>"
119 "            <pane_exif id = 'exif' expanded = 'true' >"
120 "                <entry key = 'formatted.Camera' if_set = 'true' editable = 'false' />"
121 "                <entry key = 'formatted.DateTime' if_set = 'true' editable = 'false' />"
122 "                <entry key = 'formatted.localtime' if_set = 'true' editable = 'false' />"
123 "                <entry key = 'formatted.ShutterSpeed' if_set = 'true' editable = 'false' />"
124 "                <entry key = 'formatted.Aperture' if_set = 'true' editable = 'false' />"
125 "                <entry key = 'formatted.ExposureBias' if_set = 'true' editable = 'false' />"
126 "                <entry key = 'formatted.ISOSpeedRating' if_set = 'true' editable = 'false' />"
127 "                <entry key = 'formatted.FocalLength' if_set = 'true' editable = 'false' />"
128 "                <entry key = 'formatted.FocalLength35mmFilm' if_set = 'true' editable = 'false' />"
129 "                <entry key = 'formatted.Flash' if_set = 'true' editable = 'false' />"
130 "                <entry key = 'Exif.Photo.ExposureProgram' if_set = 'true' editable = 'false' />"
131 "                <entry key = 'Exif.Photo.MeteringMode' if_set = 'true' editable = 'false' />"
132 "                <entry key = 'Exif.Photo.LightSource' if_set = 'true' editable = 'false' />"
133 "                <entry key = 'formatted.ColorProfile' if_set = 'true' editable = 'false' />"
134 "                <entry key = 'formatted.SubjectDistance' if_set = 'true' editable = 'false' />"
135 "                <entry key = 'formatted.Resolution' if_set = 'true' editable = 'false' />"
136 "                <entry key = '" ORIENTATION_KEY "' if_set = 'true' editable = 'false' />"
137 "                <entry key = 'formatted.star_rating' if_set = 'true' editable = 'false' />"
138 "            </pane_exif>"
139 "        </bar>"
140 "    </layout>"
141 "</gq>";
142
143 static const gchar default_config_file_info[] =
144 "<gq>"
145 "    <layout id = '_current_'>"
146 "        <bar>"
147 "            <pane_exif id = 'file_info' expanded = 'true' >"
148 "                <entry key = 'file.mode' if_set = 'false' editable = 'false' />"
149 "                <entry key = 'file.date' if_set = 'false' editable = 'false' />"
150 "                <entry key = 'file.size' if_set = 'false' editable = 'false' />"
151 "                <entry key = 'file.owner' if_set = 'false' editable = 'false' />"
152 "                <entry key = 'file.group' if_set = 'false' editable = 'false' />"
153 "                <entry key = 'file.class' if_set = 'false' editable = 'false' />"
154 "                <entry key = 'file.link' if_set = 'false' editable = 'false' />"
155 "            </pane_exif>"
156 "        </bar>"
157 "    </layout>"
158 "</gq>";
159
160 static const gchar default_config_location[] =
161 "<gq>"
162 "    <layout id = '_current_'>"
163 "        <bar>"
164 "            <pane_exif id = 'location' expanded = 'true' >"
165 "                <entry key = 'formatted.GPSPosition' if_set = 'true' editable = 'false' />"
166 "                <entry key = 'formatted.GPSAltitude' if_set = 'true' editable = 'false' />"
167 "                <entry key = 'formatted.timezone' if_set = 'true' editable = 'false' />"
168 "                <entry key = 'Xmp.photoshop.Country' if_set = 'false' editable = 'true' />"
169 "                <entry key = 'Xmp.iptc.CountryCode' if_set = 'false' editable = 'true' />"
170 "                <entry key = 'Xmp.photoshop.State' if_set = 'false' editable = 'true' />"
171 "                <entry key = 'Xmp.photoshop.City' if_set = 'false' editable = 'true' />"
172 "                <entry key = 'Xmp.iptc.Location' if_set = 'false' editable = 'true' />"
173 "            </pane_exif>"
174 "        </bar>"
175 "    </layout>"
176 "</gq>";
177
178 static const gchar default_config_copyright[] =
179 "<gq>"
180 "    <layout id = '_current_'>"
181 "        <bar>"
182 "            <pane_exif id = 'copyright' expanded = 'true' >"
183 "                <entry key = 'Xmp.dc.creator' if_set = 'true' editable = 'false' />"
184 "                <entry key = 'Xmp.dc.contributor' if_set = 'true' editable = 'false' />"
185 "                <entry key = 'Xmp.dc.rights' if_set = 'false' editable = 'false' />"
186 "            </pane_exif>"
187 "        </bar>"
188 "    </layout>"
189 "</gq>";
190
191 #if HAVE_LIBCHAMPLAIN
192 #if HAVE_LIBCHAMPLAIN_GTK
193 static const gchar default_config_gps[] =
194 "<gq>"
195 "    <layout id = '_current_'>"
196 "        <bar>"
197 "            <pane_gps id = 'gps' expanded = 'true'"
198 "                      map-id = 'osm-mapnik'"
199 "                      zoom-level = '8'"
200 "                      latitude = '50116666'"
201 "                      longitude = '8683333' />"
202 "        </bar>"
203 "    </layout>"
204 "</gq>";
205 #endif
206 #endif
207
208 static const KnownPanes known_panes[] = {
209 /* default sidebar */
210         {PANE_HISTOGRAM,        "histogram",    N_("Histogram"),        default_config_histogram},
211         {PANE_COMMENT,          "title",        N_("Title"),            default_config_title},
212         {PANE_KEYWORDS,         "keywords",     N_("Keywords"),         default_config_keywords},
213         {PANE_COMMENT,          "comment",      N_("Comment"),          default_config_comment},
214         {PANE_RATING,           "rating",       N_("Star Rating"),      default_config_rating},
215         {PANE_COMMENT,          "headline",     N_("Headline"),         default_config_headline},
216         {PANE_EXIF,             "exif",         N_("Exif"),             default_config_exif},
217 /* other pre-configured panes */
218         {PANE_EXIF,             "file_info",    N_("File info"),        default_config_file_info},
219         {PANE_EXIF,             "location",     N_("Location and GPS"), default_config_location},
220         {PANE_EXIF,             "copyright",    N_("Copyright"),        default_config_copyright},
221 #if HAVE_LIBCHAMPLAIN
222 #if HAVE_LIBCHAMPLAIN_GTK
223         {PANE_GPS,              "gps",  N_("GPS Map"),  default_config_gps},
224 #endif
225 #endif
226         {PANE_UNDEF,            nullptr,                nullptr,                        nullptr}
227 };
228
229 struct BarData
230 {
231         GtkWidget *widget;
232         GtkWidget *vbox;
233         FileData *fd;
234         GtkWidget *label_file_name;
235         GtkWidget *add_button;
236
237         LayoutWindow *lw;
238         gint width;
239 };
240
241 static const gchar *bar_pane_get_default_config(const gchar *id);
242
243 static void bar_expander_move(GtkWidget *, gpointer data, gboolean up, gboolean single_step)
244 {
245         auto expander = static_cast<GtkWidget *>(data);
246         GtkWidget *box;
247         gint pos;
248
249         if (!expander) return;
250         box = gtk_widget_get_ancestor(expander, GTK_TYPE_BOX);
251         if (!box) return;
252
253         gtk_container_child_get(GTK_CONTAINER(box), expander, "position", &pos, NULL);
254
255         if (single_step)
256                 {
257                 pos = up ? (pos - 1) : (pos + 1);
258                 if (pos < 0) pos = 0;
259                 }
260         else
261                 {
262                 pos = up ? 0 : -1;
263                 }
264
265         gtk_box_reorder_child(GTK_BOX(box), expander, pos);
266 }
267
268
269 static void bar_expander_move_up_cb(GtkWidget *widget, gpointer data)
270 {
271         bar_expander_move(widget, data, TRUE, TRUE);
272 }
273
274 static void bar_expander_move_down_cb(GtkWidget *widget, gpointer data)
275 {
276         bar_expander_move(widget, data, FALSE, TRUE);
277 }
278
279 static void bar_expander_move_top_cb(GtkWidget *widget, gpointer data)
280 {
281         bar_expander_move(widget, data, TRUE, FALSE);
282 }
283
284 static void bar_expander_move_bottom_cb(GtkWidget *widget, gpointer data)
285 {
286         bar_expander_move(widget, data, FALSE, FALSE);
287 }
288
289 static void height_spin_changed_cb(GtkSpinButton *spin, gpointer data)
290 {
291
292         gtk_widget_set_size_request(GTK_WIDGET(data), -1, gtk_spin_button_get_value_as_int(spin));
293 }
294
295 static void height_spin_key_press_cb(GtkEventControllerKey *, gint keyval, guint, GdkModifierType, gpointer data)
296 {
297         if ((keyval == GDK_KEY_Return || keyval == GDK_KEY_Escape))
298                 {
299                 gq_gtk_widget_destroy(GTK_WIDGET(data));
300                 }
301 }
302
303 static void expander_height_cb(GtkWindow *widget, GdkEvent *, gpointer)
304 {
305         gq_gtk_widget_destroy(GTK_WIDGET(widget));
306 }
307
308 static void bar_expander_height_cb(GtkWidget *, gpointer data)
309 {
310         auto expander = static_cast<GtkWidget *>(data);
311         GtkWidget *spin;
312         GtkWidget *window;
313         GtkWidget *data_box;
314         GList *list;
315         gint x;
316         gint y;
317         gint w;
318         gint h;
319         GdkDisplay *display;
320         GdkSeat *seat;
321         GdkDevice *device;
322         GtkEventController *controller;
323
324         display = gdk_display_get_default();
325         seat = gdk_display_get_default_seat(display);
326         device = gdk_seat_get_pointer(seat);
327         gdk_device_get_position(device, nullptr, &x, &y);
328
329         list = gtk_container_get_children(GTK_CONTAINER(expander));
330         data_box = static_cast<GtkWidget *>(list->data);
331
332 #if HAVE_GTK4
333         window = gtk_window_new();
334 #else
335         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
336 #endif
337
338         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
339         gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
340         gq_gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
341         gtk_window_set_default_size(GTK_WINDOW(window), 50, 30); //** @FIXME set these values in a more sensible way */
342         g_signal_connect(window, "key-press-event", G_CALLBACK(expander_height_cb), nullptr);
343
344         gq_gtk_window_move(GTK_WINDOW(window), x, y);
345         gtk_widget_show(window);
346
347         gtk_widget_get_size_request(GTK_WIDGET(data_box), &w, &h);
348
349         spin = gtk_spin_button_new_with_range(1, 1000, 1);
350         g_signal_connect(G_OBJECT(spin), "value-changed", G_CALLBACK(height_spin_changed_cb), data_box);
351         controller = gtk_event_controller_key_new(spin);
352         g_signal_connect(controller, "key-pressed", G_CALLBACK(height_spin_key_press_cb), window);
353
354         gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), h);
355         gq_gtk_container_add(GTK_WIDGET(window), spin);
356         gtk_widget_show(spin);
357         gtk_widget_grab_focus(GTK_WIDGET(spin));
358
359         g_list_free(list);
360 }
361
362 static void bar_expander_delete_cb(GtkWidget *, gpointer data)
363 {
364         auto expander = static_cast<GtkWidget *>(data);
365         gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(expander)), expander);
366 }
367
368 static void bar_expander_add_cb(GtkWidget *widget, gpointer)
369 {
370         const KnownPanes *pane = known_panes;
371         auto id = static_cast<const gchar *>(g_object_get_data(G_OBJECT(widget), "pane_add_id"));
372         const gchar *config;
373
374         if (!id) return;
375
376         while (pane->id)
377                 {
378                 if (strcmp(pane->id, id) == 0) break;
379                 pane++;
380                 }
381         if (!pane->id) return;
382
383         config = bar_pane_get_default_config(id);
384         if (config) load_config_from_buf(config, strlen(config), FALSE);
385
386 }
387
388
389 static void bar_menu_popup(GtkWidget *widget)
390 {
391         GtkWidget *menu;
392         GtkWidget *bar;
393         GtkWidget *expander;
394         BarData *bd;
395         gboolean display_height_option = FALSE;
396         gchar const *label;
397
398         label = gtk_expander_get_label(GTK_EXPANDER(widget));
399         display_height_option = (g_strcmp0(label, "Comment") == 0) ||
400                                                         (g_strcmp0(label, "Rating") == 0) ||
401                                                         (g_strcmp0(label, "Title") == 0) ||
402                                                         (g_strcmp0(label, "Headline") == 0) ||
403                                                         (g_strcmp0(label, "Keywords") == 0) ||
404                                                         (g_strcmp0(label, "GPS Map") == 0);
405
406         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(widget), "bar_data"));
407         if (bd)
408                 {
409                 expander = nullptr;
410                 bar = widget;
411                 }
412         else
413                 {
414                 expander = widget;
415                 bar = gtk_widget_get_parent(widget);
416                 while (bar && !g_object_get_data(G_OBJECT(bar), "bar_data"))
417                         bar = gtk_widget_get_parent(bar);
418                 if (!bar) return;
419                 }
420
421         menu = popup_menu_short_lived();
422
423         if (expander)
424                 {
425                 menu_item_add_icon(menu, _("Move to _top"), GQ_ICON_GO_TOP, G_CALLBACK(bar_expander_move_top_cb), expander);
426                 menu_item_add_icon(menu, _("Move _up"), GQ_ICON_GO_UP, G_CALLBACK(bar_expander_move_up_cb), expander);
427                 menu_item_add_icon(menu, _("Move _down"), GQ_ICON_GO_DOWN, G_CALLBACK(bar_expander_move_down_cb), expander);
428                 menu_item_add_icon(menu, _("Move to _bottom"), GQ_ICON_GO_BOTTOM, G_CALLBACK(bar_expander_move_bottom_cb), expander);
429                 menu_item_add_divider(menu);
430
431                 if (gtk_expander_get_expanded(GTK_EXPANDER(expander)) && display_height_option)
432                         {
433                         menu_item_add_icon(menu, _("Height..."), GQ_ICON_PREFERENCES, G_CALLBACK(bar_expander_height_cb), expander);
434                         menu_item_add_divider(menu);
435                         }
436
437                 menu_item_add_icon(menu, _("Remove"), GQ_ICON_DELETE, G_CALLBACK(bar_expander_delete_cb), expander);
438                 menu_item_add_divider(menu);
439                 }
440
441         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
442 }
443
444 static void bar_menu_add_popup(GtkWidget *widget)
445 {
446         GtkWidget *menu;
447         GtkWidget *bar;
448         const KnownPanes *pane = known_panes;
449
450         bar = widget;
451
452         menu = popup_menu_short_lived();
453
454         while (pane->id)
455                 {
456                 GtkWidget *item;
457                 item = menu_item_add_icon(menu, _(pane->title), GQ_ICON_ADD, G_CALLBACK(bar_expander_add_cb), bar);
458                 g_object_set_data(G_OBJECT(item), "pane_add_id", const_cast<gchar *>(pane->id));
459                 pane++;
460                 }
461
462         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
463 }
464
465
466 static gboolean bar_menu_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer)
467 {
468         if (bevent->button == MOUSE_BUTTON_RIGHT)
469                 {
470                 bar_menu_popup(widget);
471                 return TRUE;
472                 }
473         return FALSE;
474 }
475
476 static void bar_expander_cb(GObject *object, GParamSpec *, gpointer)
477 {
478         GtkExpander *expander;
479         GtkWidget *child;
480
481         expander = GTK_EXPANDER(object);
482         child = gtk_bin_get_child(GTK_BIN(expander));
483
484         if (gtk_expander_get_expanded(expander))
485                 {
486                 gq_gtk_widget_show_all(child);
487                 }
488         else
489                 {
490                 gtk_widget_hide(child);
491                 }
492 }
493
494 static gboolean bar_menu_add_cb(GtkWidget *widget, GdkEventButton *, gpointer)
495 {
496         bar_menu_add_popup(widget);
497         return TRUE;
498 }
499
500
501 static void bar_pane_set_fd_cb(GtkWidget *expander, gpointer data)
502 {
503         GtkWidget *widget = gtk_bin_get_child(GTK_BIN(expander));
504         auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
505         if (!pd) return;
506         if (pd->pane_set_fd) pd->pane_set_fd(widget, static_cast<FileData *>(data));
507 }
508
509 void bar_set_fd(GtkWidget *bar, FileData *fd)
510 {
511         BarData *bd;
512         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
513         if (!bd) return;
514
515         file_data_unref(bd->fd);
516         bd->fd = file_data_ref(fd);
517
518         gtk_container_foreach(GTK_CONTAINER(bd->vbox), bar_pane_set_fd_cb, fd);
519
520         gtk_label_set_text(GTK_LABEL(bd->label_file_name), (bd->fd) ? bd->fd->name : "");
521
522 }
523
524 static void bar_pane_notify_selection_cb(GtkWidget *expander, gpointer data)
525 {
526         GtkWidget *widget = gtk_bin_get_child(GTK_BIN(expander));
527         auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
528         if (!pd) return;
529         if (pd->pane_notify_selection) pd->pane_notify_selection(widget, GPOINTER_TO_INT(data));
530 }
531
532 void bar_notify_selection(GtkWidget *bar, gint count)
533 {
534         BarData *bd;
535         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
536         if (!bd) return;
537
538         gtk_container_foreach(GTK_CONTAINER(bd->vbox), bar_pane_notify_selection_cb, GINT_TO_POINTER(count));
539 }
540
541 gboolean bar_event(GtkWidget *bar, GdkEvent *event)
542 {
543         BarData *bd;
544         GList *list;
545         GList *work;
546         gboolean ret = FALSE;
547
548         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
549         if (!bd) return FALSE;
550
551         list = gtk_container_get_children(GTK_CONTAINER(bd->vbox));
552
553         work = list;
554         while (work)
555                 {
556                 GtkWidget *widget = gtk_bin_get_child(GTK_BIN(work->data));
557                 auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
558                 if (!pd) continue;
559
560                 if (pd->pane_event && pd->pane_event(widget, event))
561                         {
562                         ret = TRUE;
563                         break;
564                         }
565                 work = work->next;
566                 }
567         g_list_free(list);
568         return ret;
569 }
570
571 GtkWidget *bar_find_pane_by_id(GtkWidget *bar, PaneType type, const gchar *id)
572 {
573         BarData *bd;
574         GList *list;
575         GList *work;
576         GtkWidget *ret = nullptr;
577
578         if (!id || !id[0]) return nullptr;
579
580         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
581         if (!bd) return nullptr;
582
583         list = gtk_container_get_children(GTK_CONTAINER(bd->vbox));
584
585         work = list;
586         while (work)
587                 {
588                 GtkWidget *widget = gtk_bin_get_child(GTK_BIN(work->data));
589                 auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
590                 if (!pd) continue;
591
592                 if (type == pd->type && strcmp(id, pd->id) == 0)
593                         {
594                         ret = widget;
595                         break;
596                         }
597                 work = work->next;
598                 }
599         g_list_free(list);
600         return ret;
601 }
602
603 void bar_clear(GtkWidget *bar)
604 {
605         BarData *bd;
606         GList *list;
607
608         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
609         if (!bd) return;
610
611         list = gtk_container_get_children(GTK_CONTAINER(bd->vbox));
612
613         g_list_free_full(list, reinterpret_cast<GDestroyNotify>(remove_child_from_parent));
614 }
615
616 void bar_write_config(GtkWidget *bar, GString *outstr, gint indent)
617 {
618         BarData *bd;
619         GList *list;
620         GList *work;
621
622         if (!bar) return;
623
624         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
625         if (!bd) return;
626
627         WRITE_NL(); WRITE_STRING("<bar ");
628         write_bool_option(outstr, indent, "enabled", gtk_widget_get_visible(bar));
629         write_uint_option(outstr, indent, "width", bd->width);
630         WRITE_STRING(">");
631
632         indent++;
633         WRITE_NL(); WRITE_STRING("<clear/>");
634
635         list = gtk_container_get_children(GTK_CONTAINER(bd->vbox));
636         work = list;
637         while (work)
638                 {
639                 auto expander = static_cast<GtkWidget *>(work->data);
640                 GtkWidget *widget = gtk_bin_get_child(GTK_BIN(expander));
641                 auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(widget), "pane_data"));
642                 if (!pd) continue;
643
644                 pd->expanded = gtk_expander_get_expanded(GTK_EXPANDER(expander));
645
646                 if (pd->pane_write_config)
647                         pd->pane_write_config(widget, outstr, indent);
648
649                 work = work->next;
650                 }
651         g_list_free(list);
652         indent--;
653         WRITE_NL(); WRITE_STRING("</bar>");
654 }
655
656 void bar_update_expander(GtkWidget *pane)
657 {
658         auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
659         GtkWidget *expander;
660
661         if (!pd) return;
662
663         expander = gtk_widget_get_parent(pane);
664
665         gtk_expander_set_expanded(GTK_EXPANDER(expander), pd->expanded);
666 }
667
668 void bar_add(GtkWidget *bar, GtkWidget *pane)
669 {
670         GtkWidget *expander;
671         auto bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
672         auto pd = static_cast<PaneData *>(g_object_get_data(G_OBJECT(pane), "pane_data"));
673
674         if (!bd) return;
675
676         pd->lw = bd->lw;
677         pd->bar = bar;
678
679         expander = gtk_expander_new(nullptr);
680         DEBUG_NAME(expander);
681         if (pd && pd->title)
682                 {
683                 gtk_expander_set_label_widget(GTK_EXPANDER(expander), pd->title);
684                 gtk_widget_show(pd->title);
685                 }
686
687         gq_gtk_box_pack_start(GTK_BOX(bd->vbox), expander, FALSE, TRUE, 0);
688
689         g_signal_connect(expander, "button_release_event", G_CALLBACK(bar_menu_cb), bd);
690         g_signal_connect(expander, "notify::expanded", G_CALLBACK(bar_expander_cb), pd);
691
692         gq_gtk_container_add(GTK_WIDGET(expander), pane);
693
694         gtk_expander_set_expanded(GTK_EXPANDER(expander), pd->expanded);
695
696         gtk_widget_show(expander);
697
698         if (bd->fd && pd && pd->pane_set_fd) pd->pane_set_fd(pane, bd->fd);
699
700 }
701
702 void bar_populate_default(GtkWidget *)
703 {
704         const gchar *populate_id[] = {"histogram", "title", "keywords", "comment", "rating", "exif", nullptr};
705         const gchar **id = populate_id;
706
707         while (*id)
708                 {
709                 const gchar *config = bar_pane_get_default_config(*id);
710                 if (config) load_config_from_buf(config, strlen(config), FALSE);
711                 id++;
712                 }
713 }
714
715 static void bar_size_allocate(GtkWidget *, GtkAllocation *, gpointer data)
716 {
717         auto bd = static_cast<BarData *>(data);
718
719         bd->width = gtk_paned_get_position(GTK_PANED(bd->lw->utility_paned));
720 }
721
722 #pragma GCC diagnostic push
723 #pragma GCC diagnostic ignored "-Wunused-function"
724 gint bar_get_width_unused(GtkWidget *bar)
725 {
726         BarData *bd;
727
728         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
729         if (!bd) return 0;
730
731         return bd->width;
732 }
733 #pragma GCC diagnostic pop
734
735 void bar_close(GtkWidget *bar)
736 {
737         BarData *bd;
738
739         bd = static_cast<BarData *>(g_object_get_data(G_OBJECT(bar), "bar_data"));
740         if (!bd) return;
741
742         /* @FIXME This causes a g_object_unref failed error on exit */
743         gq_gtk_widget_destroy(bd->widget);
744 }
745
746 static void bar_destroy(GtkWidget *, gpointer data)
747 {
748         auto bd = static_cast<BarData *>(data);
749
750         file_data_unref(bd->fd);
751         g_free(bd);
752 }
753
754 #if HAVE_LIBCHAMPLAIN_GTK
755 /**
756    @FIXME this is an ugly hack that works around this bug:
757    https://bugzilla.gnome.org/show_bug.cgi?id=590692
758    http://bugzilla.openedhand.com/show_bug.cgi?id=1751
759    it should be removed as soon as a better solution exists
760 */
761
762 static void bar_unrealize_clutter_fix_cb(GtkWidget *widget, gpointer)
763 {
764         GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
765         if (child) gtk_widget_unrealize(child);
766 }
767 #endif
768
769 GtkWidget *bar_new(LayoutWindow *lw)
770 {
771         BarData *bd;
772         GtkWidget *box;
773         GtkWidget *scrolled;
774         GtkWidget *tbar;
775         GtkWidget *add_box;
776
777         bd = g_new0(BarData, 1);
778
779         bd->lw = lw;
780
781         bd->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
782         DEBUG_NAME(bd->widget);
783         g_object_set_data(G_OBJECT(bd->widget), "bar_data", bd);
784         g_signal_connect(G_OBJECT(bd->widget), "destroy",
785                          G_CALLBACK(bar_destroy), bd);
786
787         g_signal_connect(G_OBJECT(bd->widget), "size-allocate",
788                          G_CALLBACK(bar_size_allocate), bd);
789
790         g_signal_connect(G_OBJECT(bd->widget), "button_release_event", G_CALLBACK(bar_menu_cb), bd);
791
792         bd->width = SIDEBAR_DEFAULT_WIDTH;
793
794         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
795         DEBUG_NAME(box);
796
797         bd->label_file_name = gtk_label_new("");
798         gtk_label_set_ellipsize(GTK_LABEL(bd->label_file_name), PANGO_ELLIPSIZE_END);
799         gtk_label_set_selectable(GTK_LABEL(bd->label_file_name), TRUE);
800         gtk_label_set_xalign(GTK_LABEL(bd->label_file_name), 0.5);
801         gtk_label_set_yalign(GTK_LABEL(bd->label_file_name), 0.5);
802
803         gq_gtk_box_pack_start(GTK_BOX(box), bd->label_file_name, TRUE, TRUE, 0);
804         gtk_widget_show(bd->label_file_name);
805
806         gq_gtk_box_pack_start(GTK_BOX(bd->widget), box, FALSE, FALSE, 0);
807         gtk_widget_show(box);
808
809         scrolled = gq_gtk_scrolled_window_new(nullptr, nullptr);
810         DEBUG_NAME(scrolled);
811         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
812                 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
813         gq_gtk_box_pack_start(GTK_BOX(bd->widget), scrolled, TRUE, TRUE, 0);
814         gtk_widget_show(scrolled);
815
816
817         bd->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
818         gq_gtk_container_add(GTK_WIDGET(scrolled), bd->vbox);
819         gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(scrolled))), GTK_SHADOW_NONE);
820
821         add_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
822         DEBUG_NAME(add_box);
823         gq_gtk_box_pack_end(GTK_BOX(bd->widget), add_box, FALSE, FALSE, 0);
824         tbar = pref_toolbar_new(add_box);
825         bd->add_button = pref_toolbar_button(tbar, GQ_ICON_ADD, _("Add"), FALSE,
826                                              _("Add Pane"), G_CALLBACK(bar_menu_add_cb), bd);
827         gtk_widget_show(add_box);
828
829 #if HAVE_LIBCHAMPLAIN_GTK
830         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(scrolled))), "unrealize", G_CALLBACK(bar_unrealize_clutter_fix_cb), NULL);
831 #endif
832
833         gq_gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_NONE);
834         gtk_widget_show(bd->vbox);
835         return bd->widget;
836 }
837
838
839 GtkWidget *bar_update_from_config(GtkWidget *bar, const gchar **attribute_names, const gchar **attribute_values, LayoutWindow *lw, gboolean startup)
840 {
841         gboolean enabled = TRUE;
842         gint width = SIDEBAR_DEFAULT_WIDTH;
843
844         while (*attribute_names)
845                 {
846                 const gchar *option = *attribute_names++;
847                 const gchar *value = *attribute_values++;
848
849                 if (READ_BOOL_FULL("enabled", enabled)) continue;
850                 if (READ_INT_FULL("width", width)) continue;
851
852
853                 log_printf("unknown attribute %s = %s\n", option, value);
854                 }
855
856         if (startup)
857                 {
858                 gtk_paned_set_position(GTK_PANED(lw->utility_paned), width);
859                 }
860
861         if (enabled)
862                 {
863                 gtk_widget_show(bar);
864                 }
865         else
866                 {
867                 gtk_widget_hide(bar);
868                 }
869         return bar;
870 }
871
872 GtkWidget *bar_new_from_config(LayoutWindow *lw, const gchar **attribute_names, const gchar **attribute_values)
873 {
874         GtkWidget *bar = bar_new(lw);
875         return bar_update_from_config(bar, attribute_names, attribute_values, lw, TRUE);
876 }
877
878 GtkWidget *bar_pane_expander_title(const gchar *title)
879 {
880         GtkWidget *widget = gtk_label_new(title);
881
882         pref_label_bold(widget, TRUE, FALSE);
883         gtk_label_set_ellipsize(GTK_LABEL(widget), PANGO_ELLIPSIZE_END);
884
885         return widget;
886 }
887
888 gboolean bar_pane_translate_title(PaneType type, const gchar *id, gchar **title)
889 {
890         const KnownPanes *pane = known_panes;
891
892         if (!title) return FALSE;
893         while (pane->id)
894                 {
895                 if (pane->type == type && strcmp(pane->id, id) == 0) break;
896                 pane++;
897                 }
898         if (!pane->id) return FALSE;
899
900         if (*title && **title && strcmp(pane->title, *title) != 0) return FALSE;
901
902         g_free(*title);
903         *title = g_strdup(_(pane->title));
904         return TRUE;
905 }
906
907 static const gchar *bar_pane_get_default_config(const gchar *id)
908 {
909         const KnownPanes *pane = known_panes;
910
911         while (pane->id)
912                 {
913                 if (strcmp(pane->id, id) == 0) break;
914                 pane++;
915                 }
916         if (!pane->id) return nullptr;
917         return pane->config;
918 }
919
920 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */