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