gth-file-properties.c 12.7 KB
Newer Older
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  GThumb
 *
 *  Copyright (C) 2009 Free Software Foundation, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  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/>.
Paolo Bacchilega's avatar
Paolo Bacchilega committed
20 21 22 23 24 25 26 27 28 29
 */

#include <config.h>
#include <glib/gi18n.h>
#include "glib-utils.h"
#include "gth-file-properties.h"
#include "gth-main.h"
#include "gth-sidebar.h"
#include "gth-string-list.h"
#include "gth-time.h"
30
#include "gtk-utils.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
31 32


33
#define FONT_SCALE (0.85)
34 35
#define MIN_HEIGHT 100
#define COMMENT_DEFAULT_HEIGHT 100
Paolo Bacchilega's avatar
Paolo Bacchilega committed
36
#define CATEGORY_SIZE 1000
37
#define MAX_ATTRIBUTE_LENGTH 128
Paolo Bacchilega's avatar
Paolo Bacchilega committed
38

39 40
/* Properties */
enum {
Paolo Bacchilega's avatar
Paolo Bacchilega committed
41 42
	PROP_0,
	PROP_SHOW_DETAILS
43
};
Paolo Bacchilega's avatar
Paolo Bacchilega committed
44 45

enum {
46
	SCALE_SET_COLUMN,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
47 48 49 50 51 52 53 54 55 56 57 58 59
	WEIGHT_COLUMN,
	ID_COLUMN,
	DISPLAY_NAME_COLUMN,
	VALUE_COLUMN,
	TOOLTIP_COLUMN,
	RAW_COLUMN,
	POS_COLUMN,
	WRITEABLE_COLUMN,
	NUM_COLUMNS
};


struct _GthFilePropertiesPrivate {
60
	GtkWidget     *main_container;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
61 62
	GtkWidget     *tree_view;
	GtkListStore  *tree_model;
63
	GtkWidget     *popup_menu;
64 65
	gboolean       show_details;
	GthFileData   *last_file_data;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
66 67 68
};


69 70 71 72 73
static void gth_file_properties_gth_property_view_interface_init (GthPropertyViewInterface *iface);


G_DEFINE_TYPE_WITH_CODE (GthFileProperties,
			 gth_file_properties,
74
			 GTK_TYPE_BOX,
75 76 77
			 G_ADD_PRIVATE (GthFileProperties)
			 G_IMPLEMENT_INTERFACE (GTH_TYPE_PROPERTY_VIEW,
						gth_file_properties_gth_property_view_interface_init))
78 79


80
static gboolean
81
gth_file_properties_real_set_file (GthPropertyView *base,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
82 83
		 		   GthFileData     *file_data)
{
84
	GthFileProperties *self;
85
	gboolean           data_available;
86
	GHashTable        *category_hash;
87 88
	GList             *metadata_info;
	GList             *scan;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
89

90
	self = GTH_FILE_PROPERTIES (base);
91 92 93 94 95 96

	if (file_data != self->priv->last_file_data) {
		_g_object_unref (self->priv->last_file_data);
		self->priv->last_file_data = gth_file_data_dup (file_data);
	}

97
	gtk_list_store_clear (self->priv->tree_model);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
98

99
	if (file_data == NULL) {
100 101
		gtk_widget_hide (self->priv->main_container);
		return FALSE;
102
	}
Paolo Bacchilega's avatar
Paolo Bacchilega committed
103

104
	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0);
105

106
	data_available = FALSE;
107
	category_hash = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
108
	metadata_info = gth_main_get_all_metadata_info ();
109 110
	for (scan = metadata_info; scan; scan = scan->next) {
		GthMetadataInfo     *info = scan->data;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
111 112
		char                *value;
		char                *tooltip;
113
		GthMetadataCategory *category;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
114 115 116 117 118
		GtkTreeIter          iter;

		if ((info->flags & GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW) != GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW)
			continue;

Paolo Bacchilega's avatar
Paolo Bacchilega committed
119
		value = gth_file_data_get_attribute_as_string (file_data, info->id);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
120 121
		if ((value == NULL) || (*value == '\0')) {
			g_free (value);
122
			continue;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
123
		}
124

125 126 127 128 129
		if (info->id != NULL) {
			if (g_str_has_prefix (info->id, "Exif")) {
				if (! self->priv->show_details)
					continue;
			}
130
			else if (g_str_has_prefix (info->id, "Iptc")) {
131 132 133
				if (! self->priv->show_details)
					continue;
			}
134
			else if (g_str_has_prefix (info->id, "Xmp")) {
135 136 137
				if (! self->priv->show_details)
					continue;
			}
138 139 140
			else
				if (self->priv->show_details)
					continue;
141 142
		}

Paolo Bacchilega's avatar
Paolo Bacchilega committed
143
		if (value != NULL) {
144
			char *utf8_value;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
145 146
			char *tmp_value;

147 148 149 150
			utf8_value = _g_utf8_from_any (value);
			if (g_utf8_strlen (utf8_value, -1) > MAX_ATTRIBUTE_LENGTH)
				g_utf8_strncpy (g_utf8_offset_to_pointer (utf8_value, MAX_ATTRIBUTE_LENGTH - 1), "…", 1);
			tmp_value = _g_utf8_replace (utf8_value, "[\r\n]", " ");
Paolo Bacchilega's avatar
Paolo Bacchilega committed
151 152
			g_free (value);
			value = tmp_value;
153 154

			g_free (utf8_value);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
155
		}
156
		tooltip = g_markup_printf_escaped ("%s: %s", _(info->display_name), value);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
157

158 159 160
		category = g_hash_table_lookup (category_hash, info->category);
		if (category == NULL) {
			category = gth_main_get_metadata_category (info->category);
161 162
			gtk_list_store_append (self->priv->tree_model, &iter);
			gtk_list_store_set (self->priv->tree_model, &iter,
163
					    SCALE_SET_COLUMN, FALSE,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
164 165
					    WEIGHT_COLUMN, PANGO_WEIGHT_BOLD,
					    ID_COLUMN, category->id,
166
					    DISPLAY_NAME_COLUMN, _(category->display_name),
Paolo Bacchilega's avatar
Paolo Bacchilega committed
167 168
					    POS_COLUMN, category->sort_order * CATEGORY_SIZE,
					    -1);
169
			g_hash_table_insert (category_hash, g_strdup (info->category), category);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
170 171
		}

172 173
		gtk_list_store_append (self->priv->tree_model, &iter);
		gtk_list_store_set (self->priv->tree_model,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
174
				    &iter,
175
				    SCALE_SET_COLUMN, TRUE,
176
				    WEIGHT_COLUMN, PANGO_WEIGHT_NORMAL,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
177
				    ID_COLUMN, info->id,
178
				    DISPLAY_NAME_COLUMN, _(info->display_name),
Paolo Bacchilega's avatar
Paolo Bacchilega committed
179 180 181 182 183 184 185
				    VALUE_COLUMN, value,
				    TOOLTIP_COLUMN, tooltip,
				    POS_COLUMN, (category->sort_order * CATEGORY_SIZE) + info->sort_order,
				    -1);

		g_free (tooltip);
		g_free (value);
186 187

		data_available = TRUE;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
188
	}
189
	g_list_free (metadata_info);
190

191 192
	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), POS_COLUMN, GTK_SORT_ASCENDING);
	gtk_tree_view_expand_all (GTK_TREE_VIEW (self->priv->tree_view));
193
	gtk_widget_set_visible (self->priv->main_container, data_available);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
194

195
	g_hash_table_destroy (category_hash);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
196

197
	return data_available;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
198 199 200
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
201
static const char *
202
gth_file_properties_real_get_name (GthPropertyView *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
203 204 205 206 207
{
	return _("Properties");
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
208
static const char *
209
gth_file_properties_real_get_icon (GthPropertyView *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
210
{
211
	return "document-properties-symbolic";
Paolo Bacchilega's avatar
Paolo Bacchilega committed
212 213 214
}


215 216 217 218 219 220 221 222 223
static void
gth_file_properties_finalize (GObject *base)
{
	GthFileProperties *self;

	self = (GthFileProperties *) base;

	if (self->priv->popup_menu != NULL)
		gtk_widget_destroy (self->priv->popup_menu);
224
	_g_object_unref (self->priv->last_file_data);
225

226
	G_OBJECT_CLASS (gth_file_properties_parent_class)->finalize (base);
227 228 229
}


230 231 232 233 234 235 236 237
static void
gth_file_properties_set_property (GObject      *object,
				  guint         property_id,
				  const GValue *value,
				  GParamSpec   *pspec)
{
	GthFileProperties *self;

238
	self = GTH_FILE_PROPERTIES (object);
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

	switch (property_id) {
	case PROP_SHOW_DETAILS:
		self->priv->show_details = g_value_get_boolean (value);
		break;
	default:
		break;
	}
}


static void
gth_file_properties_get_property (GObject    *object,
				  guint       property_id,
				  GValue     *value,
				  GParamSpec *pspec)
{
	GthFileProperties *self;

258
	self = GTH_FILE_PROPERTIES (object);
259 260 261 262 263 264 265 266 267 268 269 270

	switch (property_id) {
	case PROP_SHOW_DETAILS:
		g_value_set_boolean (value, self->priv->show_details);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
271 272 273
static void
gth_file_properties_class_init (GthFilePropertiesClass *klass)
{
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (klass);
	object_class->set_property = gth_file_properties_set_property;
	object_class->get_property = gth_file_properties_get_property;
	object_class->finalize = gth_file_properties_finalize;

	/* properties */

	g_object_class_install_property (object_class,
					 PROP_SHOW_DETAILS,
					 g_param_spec_boolean ("show-details",
							       "Show details",
							       "Whether to show all the file properties",
							       FALSE,
							       G_PARAM_READWRITE));
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
}


static void
copy_menu_item_activate_cb (GtkMenuItem *menuitem,
			    gpointer     user_data)
{
	GthFileProperties *self = user_data;
	GtkTreeModel      *model;
	GtkTreeIter        iter;
	char              *value;

	if (! gtk_tree_selection_get_selected (GTK_TREE_SELECTION (gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view))), &model, &iter))
		return;

305 306 307 308 309
	gtk_tree_model_get (model, &iter, VALUE_COLUMN, &value, -1);
	if (value != NULL)
		gtk_clipboard_set_text (gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (menuitem)), GDK_SELECTION_CLIPBOARD),
					value,
					-1);
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334

	g_free (value);
}


static gboolean
tree_view_button_press_event_cb (GtkWidget      *widget,
				 GdkEventButton *event,
				 gpointer        user_data)
{
	GthFileProperties *self = user_data;

	if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) {
		GtkTreePath *path;

		if (! gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->tree_view),
						     event->x,
						     event->y,
						     &path,
						     NULL,
						     NULL,
						     NULL))
			return FALSE;

		gtk_tree_selection_select_path (GTK_TREE_SELECTION (gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view))), path);
335
		gtk_menu_popup_at_pointer (GTK_MENU (self->priv->popup_menu), (GdkEvent *) event);
336 337 338 339 340 341 342

		gtk_tree_path_free (path);

		return TRUE;
	}

	return FALSE;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
343 344 345
}


346 347 348 349 350
static gboolean
tree_view_popup_menu_cb (GtkWidget *widget,
			 gpointer   user_data)
{
	GthFileProperties *self = user_data;
351
	gtk_menu_popup_at_pointer (GTK_MENU (self->priv->popup_menu), NULL);
352 353 354 355
	return TRUE;
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
356
static void
357
gth_file_properties_init (GthFileProperties *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
358 359
{
	GtkWidget         *scrolled_win;
360
	GtkWidget         *menu_item;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
361 362 363
	GtkCellRenderer   *renderer;
	GtkTreeViewColumn *column;

364
	self->priv = gth_file_properties_get_instance_private (self);
365
	self->priv->show_details = FALSE;
366 367
	self->priv->last_file_data = NULL;

368
	gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
369
	gtk_box_set_spacing (GTK_BOX (self), 6);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
370

371 372 373
	self->priv->main_container = scrolled_win = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
374
	gtk_widget_show (scrolled_win);
375
	gtk_widget_set_size_request (scrolled_win, -1, MIN_HEIGHT);
376
	gtk_box_pack_start (GTK_BOX (self), scrolled_win, TRUE, TRUE, 0);
377

378 379 380 381
	self->priv->tree_view = gtk_tree_view_new ();
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->tree_view), FALSE);
	gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (self->priv->tree_view), TOOLTIP_COLUMN);
	self->priv->tree_model = gtk_list_store_new (NUM_COLUMNS,
382
						     G_TYPE_BOOLEAN,
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
						     PANGO_TYPE_WEIGHT,
						     G_TYPE_STRING,
						     G_TYPE_STRING,
						     G_TYPE_STRING,
						     G_TYPE_STRING,
						     G_TYPE_STRING,
						     G_TYPE_INT,
						     G_TYPE_BOOLEAN);
	gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->tree_view),
				 GTK_TREE_MODEL (self->priv->tree_model));
	g_object_unref (self->priv->tree_model);
	gtk_widget_show (self->priv->tree_view);
	gtk_container_add (GTK_CONTAINER (scrolled_win), self->priv->tree_view);

	/* popup menu */

	self->priv->popup_menu = gtk_menu_new ();
400
	menu_item = gtk_menu_item_new_with_label (_GTK_LABEL_COPY);
401 402 403 404 405 406 407 408 409 410 411
	gtk_widget_show (menu_item);
	gtk_menu_shell_append (GTK_MENU_SHELL (self->priv->popup_menu),
			       menu_item);
	g_signal_connect (menu_item,
			  "activate",
			  G_CALLBACK (copy_menu_item_activate_cb),
			  self);
	g_signal_connect (self->priv->tree_view,
			  "button-press-event",
			  G_CALLBACK (tree_view_button_press_event_cb),
			  self);
412 413 414 415
	g_signal_connect (self->priv->tree_view,
			  "popup-menu",
			  G_CALLBACK (tree_view_popup_menu_cb),
			  self);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
416 417 418 419 420 421 422

	/**/

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ("",
							   renderer,
							   "text", DISPLAY_NAME_COLUMN,
423
							   "scale-set", SCALE_SET_COLUMN,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
424 425 426 427
							   "weight", WEIGHT_COLUMN,
							   NULL);
	g_object_set (renderer,
		      "ellipsize", PANGO_ELLIPSIZE_END,
428
		      "scale", FONT_SCALE,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
429 430 431
		      NULL);

	gtk_tree_view_column_set_expand (column, TRUE);
432
	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
433
	gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->tree_view),
Paolo Bacchilega's avatar
Paolo Bacchilega committed
434 435 436 437 438 439 440 441 442 443 444
				     column);

	/**/

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes ("",
							   renderer,
							   "text", VALUE_COLUMN,
							   NULL);
	g_object_set (renderer,
		      "ellipsize", PANGO_ELLIPSIZE_END,
445
		      "scale", FONT_SCALE,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
446 447 448
		      NULL);

	gtk_tree_view_column_set_expand (column, TRUE);
449
	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
450
	gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->tree_view),
Paolo Bacchilega's avatar
Paolo Bacchilega committed
451 452
				     column);

453
	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->tree_model), POS_COLUMN, GTK_SORT_ASCENDING);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
454 455 456 457
}


static void
458
gth_file_properties_gth_property_view_interface_init (GthPropertyViewInterface *iface)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
459 460 461 462 463
{
	iface->get_name = gth_file_properties_real_get_name;
	iface->get_icon = gth_file_properties_real_get_icon;
	iface->set_file = gth_file_properties_real_set_file;
}