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