1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2009 Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <glib/gi18n.h>
24 #include "dlg-favorite-properties.h"
25 #include "glib-utils.h"
26 #include "gth-file-properties.h"
27 #include "gth-main.h"
28 #include "gth-preferences.h"
29 #include "gth-property-view.h"
30 #include "gth-string-list.h"
31 #include "gth-time.h"
32 #include "gtk-utils.h"
33
34
35 #define FONT_SCALE (0.85)
36 #define MIN_HEIGHT 100
37 #define COMMENT_DEFAULT_HEIGHT 100
38 #define CATEGORY_SIZE 1000
39 #define MAX_ATTRIBUTE_LENGTH 128
40
41 /* Properties */
42 enum {
43 PROP_0,
44 PROP_SHOW_DETAILS
45 };
46
47 enum {
48 SCALE_SET_COLUMN,
49 WEIGHT_COLUMN,
50 ID_COLUMN,
51 DISPLAY_NAME_COLUMN,
52 VALUE_COLUMN,
53 TOOLTIP_COLUMN,
54 RAW_COLUMN,
55 POS_COLUMN,
56 WRITEABLE_COLUMN,
57 NUM_COLUMNS
58 };
59
60
61 struct _GthFilePropertiesPrivate {
62 GSettings *settings;
63 GtkWidget *main_container;
64 GtkWidget *tree_view;
65 GtkListStore *tree_model;
66 GtkWidget *popup_menu;
67 GtkWidget *copy_menu_item;
68 GtkWidget *edit_favorites_menu_item;
69 GtkWidget *context_menu_sep;
70 gboolean show_details;
71 GthFileData *last_file_data;
72 GHashTable *favorites;
73 gboolean default_favorites;
74 };
75
76
77 static void gth_file_properties_gth_property_view_interface_init (GthPropertyViewInterface *iface);
78
79
G_DEFINE_TYPE_WITH_CODE(GthFileProperties,gth_file_properties,GTK_TYPE_BOX,G_ADD_PRIVATE (GthFileProperties)G_IMPLEMENT_INTERFACE (GTH_TYPE_PROPERTY_VIEW,gth_file_properties_gth_property_view_interface_init))80 G_DEFINE_TYPE_WITH_CODE (GthFileProperties,
81 gth_file_properties,
82 GTK_TYPE_BOX,
83 G_ADD_PRIVATE (GthFileProperties)
84 G_IMPLEMENT_INTERFACE (GTH_TYPE_PROPERTY_VIEW,
85 gth_file_properties_gth_property_view_interface_init))
86
87
88 static gboolean
89 gth_file_properties_real_can_view (GthPropertyView *base,
90 GthFileData *file_data)
91 {
92 GthFileProperties *self = GTH_FILE_PROPERTIES (base);
93 gboolean data_available;
94 GList *metadata_info;
95 GList *scan;
96
97 if (file_data == NULL)
98 return FALSE;
99
100 if (! self->priv->show_details)
101 return TRUE;
102
103 data_available = FALSE;
104 metadata_info = gth_main_get_all_metadata_info ();
105 for (scan = metadata_info; scan; scan = scan->next) {
106 GthMetadataInfo *info = scan->data;
107 char *value;
108
109 if ((info->flags & GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW) != GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW)
110 continue;
111
112 value = gth_file_data_get_attribute_as_string (file_data, info->id);
113 if ((value == NULL) || (*value == '\0')) {
114 g_free (value);
115 continue;
116 }
117
118 if (info->id != NULL) {
119 if (g_str_has_prefix (info->id, "Exif")) {
120 data_available = TRUE;
121 break;
122 }
123 else if (g_str_has_prefix (info->id, "Iptc")) {
124 data_available = TRUE;
125 break;
126 }
127 else if (g_str_has_prefix (info->id, "Xmp")) {
128 data_available = TRUE;
129 break;
130 }
131 }
132 }
133
134 return data_available;
135 }
136
137
138 static void
gth_file_properties_real_set_file(GthPropertyView * base,GthFileData * file_data)139 gth_file_properties_real_set_file (GthPropertyView *base,
140 GthFileData *file_data)
141 {
142 GthFileProperties *self;
143 GHashTable *category_hash;
144 GList *metadata_info;
145 GList *scan;
146
147 self = GTH_FILE_PROPERTIES (base);
148
149 if (file_data != self->priv->last_file_data) {
150 _g_object_unref (self->priv->last_file_data);
151 self->priv->last_file_data = gth_file_data_dup (file_data);
152 }
153
154 gtk_list_store_clear (self->priv->tree_model);
155
156 if (file_data == NULL)
157 return;
158
159 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0);
160
161 category_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
162 metadata_info = gth_main_get_all_metadata_info ();
163 for (scan = metadata_info; scan; scan = scan->next) {
164 GthMetadataInfo *info = scan->data;
165 char *value;
166 char *tooltip;
167 GthMetadataCategory *category;
168 GtkTreeIter iter;
169
170 if ((info->flags & GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW) != GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW)
171 continue;
172
173 if (info->id == NULL)
174 continue;
175
176 value = gth_file_data_get_attribute_as_string (file_data, info->id);
177 if ((value == NULL) || (*value == '\0')) {
178 g_free (value);
179 continue;
180 }
181
182 if (! self->priv->show_details && ! self->priv->default_favorites) {
183 if (! g_hash_table_contains (self->priv->favorites, info->id))
184 continue;
185 }
186 else if (g_str_has_prefix (info->id, "Exif")) {
187 if (! self->priv->show_details)
188 continue;
189 }
190 else if (g_str_has_prefix (info->id, "Iptc")) {
191 if (! self->priv->show_details)
192 continue;
193 }
194 else if (g_str_has_prefix (info->id, "Xmp")) {
195 if (! self->priv->show_details)
196 continue;
197 }
198 else
199 if (self->priv->show_details)
200 continue;
201
202 if (value != NULL) {
203 char *utf8_value;
204 char *tmp_value;
205
206 utf8_value = _g_utf8_from_any (value);
207 if (g_utf8_strlen (utf8_value, -1) > MAX_ATTRIBUTE_LENGTH)
208 g_utf8_strncpy (g_utf8_offset_to_pointer (utf8_value, MAX_ATTRIBUTE_LENGTH - 1), "…", 1);
209 tmp_value = _g_utf8_replace_pattern (utf8_value, "[\r\n]", " ");
210 g_free (value);
211 value = tmp_value;
212
213 g_free (utf8_value);
214 }
215 tooltip = g_markup_printf_escaped ("%s: %s", _(info->display_name), value);
216
217 category = g_hash_table_lookup (category_hash, info->category);
218 if (category == NULL) {
219 category = gth_main_get_metadata_category (info->category);
220 gtk_list_store_append (self->priv->tree_model, &iter);
221 gtk_list_store_set (self->priv->tree_model, &iter,
222 SCALE_SET_COLUMN, FALSE,
223 WEIGHT_COLUMN, PANGO_WEIGHT_BOLD,
224 ID_COLUMN, category->id,
225 DISPLAY_NAME_COLUMN, _(category->display_name),
226 POS_COLUMN, category->sort_order * CATEGORY_SIZE,
227 -1);
228 g_hash_table_insert (category_hash, g_strdup (info->category), category);
229 }
230
231 gtk_list_store_append (self->priv->tree_model, &iter);
232 gtk_list_store_set (self->priv->tree_model,
233 &iter,
234 SCALE_SET_COLUMN, TRUE,
235 WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
236 ID_COLUMN, info->id,
237 DISPLAY_NAME_COLUMN, _(info->display_name),
238 VALUE_COLUMN, value,
239 TOOLTIP_COLUMN, tooltip,
240 POS_COLUMN, (category->sort_order * CATEGORY_SIZE) + info->sort_order,
241 -1);
242
243 g_free (tooltip);
244 g_free (value);
245 }
246 g_list_free (metadata_info);
247
248 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), POS_COLUMN, GTK_SORT_ASCENDING);
249 gtk_tree_view_expand_all (GTK_TREE_VIEW (self->priv->tree_view));
250
251 g_hash_table_destroy (category_hash);
252 }
253
254
255 static const char *
gth_file_properties_real_get_name(GthPropertyView * self)256 gth_file_properties_real_get_name (GthPropertyView *self)
257 {
258 return _("Properties");
259 }
260
261
262 static const char *
gth_file_properties_real_get_icon(GthPropertyView * self)263 gth_file_properties_real_get_icon (GthPropertyView *self)
264 {
265 return "document-properties-symbolic";
266 }
267
268
269 static void
gth_file_properties_finalize(GObject * base)270 gth_file_properties_finalize (GObject *base)
271 {
272 GthFileProperties *self;
273
274 self = (GthFileProperties *) base;
275
276 if (self->priv->popup_menu != NULL)
277 gtk_widget_destroy (self->priv->popup_menu);
278 _g_object_unref (self->priv->last_file_data);
279 _g_object_unref (self->priv->settings);
280 if (self->priv->favorites != NULL)
281 g_hash_table_unref (self->priv->favorites);
282
283 G_OBJECT_CLASS (gth_file_properties_parent_class)->finalize (base);
284 }
285
286
287 static void
update_favorites(GthFileProperties * self)288 update_favorites (GthFileProperties *self)
289 {
290 char *favorites;
291
292 if (self->priv->show_details)
293 return;
294
295 favorites = g_settings_get_string (self->priv->settings, PREF_BROWSER_FAVORITE_PROPERTIES);
296
297 if (self->priv->favorites != NULL)
298 g_hash_table_unref (self->priv->favorites);
299 self->priv->favorites = _g_str_split_as_hash_table (favorites);
300 self->priv->default_favorites = _g_str_equal (favorites, "default");
301
302 g_free (favorites);
303 }
304
305
306 static void
update_context_menu_separator_visibility(GthFileProperties * self)307 update_context_menu_separator_visibility (GthFileProperties *self)
308 {
309 int n_visible;
310
311 n_visible = 0;
312 if (gtk_widget_get_visible (self->priv->copy_menu_item))
313 n_visible++;
314 if (gtk_widget_get_visible (self->priv->edit_favorites_menu_item))
315 n_visible++;
316 gtk_widget_set_visible (self->priv->context_menu_sep, n_visible > 1);
317 }
318
319
320 static void
gth_file_properties_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)321 gth_file_properties_set_property (GObject *object,
322 guint property_id,
323 const GValue *value,
324 GParamSpec *pspec)
325 {
326 GthFileProperties *self;
327
328 self = GTH_FILE_PROPERTIES (object);
329
330 switch (property_id) {
331 case PROP_SHOW_DETAILS:
332 self->priv->show_details = g_value_get_boolean (value);
333 update_favorites (self);
334 if (self->priv->edit_favorites_menu_item != NULL) {
335 gtk_widget_set_visible (self->priv->edit_favorites_menu_item, ! self->priv->show_details);
336 update_context_menu_separator_visibility (self);
337 }
338 break;
339 default:
340 break;
341 }
342 }
343
344
345 static void
gth_file_properties_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)346 gth_file_properties_get_property (GObject *object,
347 guint property_id,
348 GValue *value,
349 GParamSpec *pspec)
350 {
351 GthFileProperties *self;
352
353 self = GTH_FILE_PROPERTIES (object);
354
355 switch (property_id) {
356 case PROP_SHOW_DETAILS:
357 g_value_set_boolean (value, self->priv->show_details);
358 break;
359 default:
360 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
361 break;
362 }
363 }
364
365
366 static void
gth_file_properties_class_init(GthFilePropertiesClass * klass)367 gth_file_properties_class_init (GthFilePropertiesClass *klass)
368 {
369 GObjectClass *object_class;
370
371 object_class = G_OBJECT_CLASS (klass);
372 object_class->set_property = gth_file_properties_set_property;
373 object_class->get_property = gth_file_properties_get_property;
374 object_class->finalize = gth_file_properties_finalize;
375
376 /* properties */
377
378 g_object_class_install_property (object_class,
379 PROP_SHOW_DETAILS,
380 g_param_spec_boolean ("show-details",
381 "Show details",
382 "Whether to show all the file properties",
383 FALSE,
384 G_PARAM_READWRITE));
385 }
386
387
388 static void
copy_menu_item_activate_cb(GtkMenuItem * menuitem,gpointer user_data)389 copy_menu_item_activate_cb (GtkMenuItem *menuitem,
390 gpointer user_data)
391 {
392 GthFileProperties *self = user_data;
393 GtkTreeModel *model;
394 GtkTreeIter iter;
395 char *value;
396
397 if (! gtk_tree_selection_get_selected (GTK_TREE_SELECTION (gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view))), &model, &iter))
398 return;
399
400 gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
401 if (value != NULL)
402 gtk_clipboard_set_text (gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (menuitem)), GDK_SELECTION_CLIPBOARD),
403 value,
404 -1);
405
406 g_free (value);
407 }
408
409
410 static void
edit_favorites_menu_item_activate_cb(GtkMenuItem * menuitem,gpointer user_data)411 edit_favorites_menu_item_activate_cb (GtkMenuItem *menuitem,
412 gpointer user_data)
413 {
414 GthFileProperties *self = user_data;
415 dlg_favorite_properties (GTH_BROWSER (_gtk_widget_get_toplevel_if_window (GTK_WIDGET (self))));
416 }
417
418
419 static gboolean
tree_view_button_press_event_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)420 tree_view_button_press_event_cb (GtkWidget *widget,
421 GdkEventButton *event,
422 gpointer user_data)
423 {
424 GthFileProperties *self = user_data;
425
426 if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) {
427 GtkTreePath *path;
428 gboolean path_selected;
429
430 path_selected = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->tree_view),
431 event->x,
432 event->y,
433 &path,
434 NULL,
435 NULL,
436 NULL);
437
438 gtk_widget_set_visible (self->priv->copy_menu_item, path_selected);
439 update_context_menu_separator_visibility (self);
440
441 if (path != NULL) {
442 gtk_tree_selection_select_path (GTK_TREE_SELECTION (gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view))), path);
443 gtk_tree_path_free (path);
444 }
445
446 gtk_menu_popup_at_pointer (GTK_MENU (self->priv->popup_menu), (GdkEvent *) event);
447
448 return TRUE;
449 }
450
451 return FALSE;
452 }
453
454
455 static gboolean
tree_view_popup_menu_cb(GtkWidget * widget,gpointer user_data)456 tree_view_popup_menu_cb (GtkWidget *widget,
457 gpointer user_data)
458 {
459 GthFileProperties *self = user_data;
460
461 gtk_widget_set_visible (self->priv->copy_menu_item, TRUE);
462 update_context_menu_separator_visibility (self);
463
464 gtk_menu_popup_at_pointer (GTK_MENU (self->priv->popup_menu), NULL);
465
466 return TRUE;
467 }
468
469
470 static void
pref_favorite_properties_changed(GSettings * settings,const char * key,gpointer user_data)471 pref_favorite_properties_changed (GSettings *settings,
472 const char *key,
473 gpointer user_data)
474 {
475 GthFileProperties *self = user_data;
476
477 update_favorites (self);
478 gth_property_view_set_file (GTH_PROPERTY_VIEW (self), self->priv->last_file_data);
479 }
480
481
482 static void
gth_file_properties_init(GthFileProperties * self)483 gth_file_properties_init (GthFileProperties *self)
484 {
485 GtkWidget *scrolled_win;
486 GtkWidget *menu_item;
487 GtkCellRenderer *renderer;
488 GtkTreeViewColumn *column;
489
490 self->priv = gth_file_properties_get_instance_private (self);
491 self->priv->show_details = FALSE;
492 self->priv->last_file_data = NULL;
493 self->priv->settings = g_settings_new (GTHUMB_BROWSER_SCHEMA);
494 self->priv->favorites = NULL;
495
496 update_favorites (self);
497
498 g_signal_connect (self->priv->settings,
499 "changed::" PREF_BROWSER_FAVORITE_PROPERTIES,
500 G_CALLBACK (pref_favorite_properties_changed),
501 self);
502
503 gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
504 gtk_box_set_spacing (GTK_BOX (self), 6);
505
506 self->priv->main_container = scrolled_win = gtk_scrolled_window_new (NULL, NULL);
507 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
508 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);
509 gtk_widget_show (scrolled_win);
510 gtk_widget_set_size_request (scrolled_win, -1, MIN_HEIGHT);
511 gtk_box_pack_start (GTK_BOX (self), scrolled_win, TRUE, TRUE, 0);
512
513 self->priv->tree_view = gtk_tree_view_new ();
514 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->tree_view), FALSE);
515 gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (self->priv->tree_view), TOOLTIP_COLUMN);
516 self->priv->tree_model = gtk_list_store_new (NUM_COLUMNS,
517 G_TYPE_BOOLEAN,
518 PANGO_TYPE_WEIGHT,
519 G_TYPE_STRING,
520 G_TYPE_STRING,
521 G_TYPE_STRING,
522 G_TYPE_STRING,
523 G_TYPE_STRING,
524 G_TYPE_INT,
525 G_TYPE_BOOLEAN);
526 gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->tree_view),
527 GTK_TREE_MODEL (self->priv->tree_model));
528 g_object_unref (self->priv->tree_model);
529 gtk_widget_show (self->priv->tree_view);
530 gtk_container_add (GTK_CONTAINER (scrolled_win), self->priv->tree_view);
531
532 /* popup menu */
533
534 self->priv->popup_menu = gtk_menu_new ();
535
536 self->priv->copy_menu_item = menu_item = gtk_menu_item_new_with_label (_GTK_LABEL_COPY);
537 gtk_widget_show (menu_item);
538 gtk_menu_shell_append (GTK_MENU_SHELL (self->priv->popup_menu), menu_item);
539 g_signal_connect (menu_item,
540 "activate",
541 G_CALLBACK (copy_menu_item_activate_cb),
542 self);
543
544 self->priv->context_menu_sep = menu_item = gtk_separator_menu_item_new ();
545 gtk_widget_show (menu_item);
546 gtk_menu_shell_append (GTK_MENU_SHELL (self->priv->popup_menu), menu_item);
547
548 self->priv->edit_favorites_menu_item = menu_item = gtk_menu_item_new_with_label (_("Preferences"));
549 gtk_widget_set_visible (menu_item, ! self->priv->show_details);
550 gtk_menu_shell_append (GTK_MENU_SHELL (self->priv->popup_menu), menu_item);
551 g_signal_connect (menu_item,
552 "activate",
553 G_CALLBACK (edit_favorites_menu_item_activate_cb),
554 self);
555
556 update_context_menu_separator_visibility (self);
557
558 g_signal_connect (self->priv->tree_view,
559 "button-press-event",
560 G_CALLBACK (tree_view_button_press_event_cb),
561 self);
562 g_signal_connect (self->priv->tree_view,
563 "popup-menu",
564 G_CALLBACK (tree_view_popup_menu_cb),
565 self);
566
567 /**/
568
569 renderer = gtk_cell_renderer_text_new ();
570 column = gtk_tree_view_column_new_with_attributes ("",
571 renderer,
572 "text", DISPLAY_NAME_COLUMN,
573 "scale-set", SCALE_SET_COLUMN,
574 "weight", WEIGHT_COLUMN,
575 NULL);
576 g_object_set (renderer,
577 "ellipsize", PANGO_ELLIPSIZE_END,
578 "scale", FONT_SCALE,
579 NULL);
580
581 gtk_tree_view_column_set_expand (column, TRUE);
582 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
583 gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->tree_view),
584 column);
585
586 /**/
587
588 renderer = gtk_cell_renderer_text_new ();
589 column = gtk_tree_view_column_new_with_attributes ("",
590 renderer,
591 "text", VALUE_COLUMN,
592 NULL);
593 g_object_set (renderer,
594 "ellipsize", PANGO_ELLIPSIZE_END,
595 "scale", FONT_SCALE,
596 NULL);
597
598 gtk_tree_view_column_set_expand (column, TRUE);
599 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
600 gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->tree_view),
601 column);
602
603 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), POS_COLUMN, GTK_SORT_ASCENDING);
604 }
605
606
607 static void
gth_file_properties_gth_property_view_interface_init(GthPropertyViewInterface * iface)608 gth_file_properties_gth_property_view_interface_init (GthPropertyViewInterface *iface)
609 {
610 iface->get_name = gth_file_properties_real_get_name;
611 iface->get_icon = gth_file_properties_real_get_icon;
612 iface->can_view = gth_file_properties_real_can_view;
613 iface->set_file = gth_file_properties_real_set_file;
614 }
615