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