gth-image-viewer-page.c 71.6 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
 */

#include <config.h>
#include <math.h>
#include <gdk/gdkkeysyms.h>
#include <gthumb.h>
26
#include "actions.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
27
#include "gth-image-viewer-page.h"
28
#include "preferences.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
29

30

31
#define UPDATE_QUALITY_DELAY 200
32
#define UPDATE_VISIBILITY_DELAY 100
33
#define N_HEADER_BAR_BUTTONS 7
34
#define HIDE_OVERVIEW_TIMEOUT 2 /* in seconds */
35
#define OVERLAY_MARGIN 10
Paolo Bacchilega's avatar
Paolo Bacchilega committed
36 37
#define ZOOM_BUTTON 2
#define APPLY_ICC_PROFILE_BUTTON 3
38 39
#define TOGGLE_ANIMATION_BUTTON 4
#define STEP_ANIMATION_BUTTON 5
40
#define TRANSPARENCY_STYLE_BUTTON 6
41
#undef ALWAYS_LOAD_ORIGINAL_SIZE
42 43
#define N_FORWARD_PRELOADERS 2
#define N_BACKWARD_PRELOADERS 2
44 45


46 47 48
static void gth_viewer_page_interface_init (GthViewerPageInterface *iface);


49 50 51 52 53
static const GActionEntry actions[] = {
	{ "image-zoom-in", gth_browser_activate_image_zoom_in },
	{ "image-zoom-out", gth_browser_activate_image_zoom_out },
	{ "image-zoom-100", gth_browser_activate_image_zoom_100 },
	{ "image-zoom-fit", gth_browser_activate_image_zoom_fit },
Paolo Bacchilega's avatar
Paolo Bacchilega committed
54
	{ "image-zoom-fit-if-larger", gth_browser_activate_image_zoom_fit_if_larger },
55
	{ "image-zoom-fit-width", gth_browser_activate_image_zoom_fit_width },
56
	{ "image-zoom-fit-height", gth_browser_activate_image_zoom_fit_height },
57 58 59 60
	{ "image-undo", gth_browser_activate_image_undo },
	{ "image-redo", gth_browser_activate_image_redo },
	{ "copy-image", gth_browser_activate_copy_image },
	{ "paste-image", gth_browser_activate_paste_image },
61
	{ "apply-icc-profile", toggle_action_activated, NULL, "true", gth_browser_activate_apply_icc_profile },
62 63
	{ "toggle-animation", toggle_action_activated, NULL, "true", gth_browser_activate_toggle_animation },
	{ "step-animation", gth_browser_activate_step_animation },
Paolo Bacchilega's avatar
Paolo Bacchilega committed
64
	{ "image-zoom", gth_browser_activate_image_zoom, "s", "''", NULL },
65
	{ "transparency-style", gth_browser_activate_transparency_style, "s", "''", NULL },
66 67 68 69 70 71 72 73 74 75 76 77 78
};


static const GthAccelerator accelerators[] = {
	{ "image-zoom-in", "<control>plus" },
	{ "image-zoom-out", "<control>minus" },
	{ "image-zoom-100", "<control>0" },
	{ "image-undo", "<control>z" },
	{ "image-redo", "<shift><control>z" },
};


static const GthMenuEntry file_popup_entries[] = {
79 80
	{ N_("Copy Image"), "win.copy-image" },
	{ N_("Paste Image"), "win.paste-image" },
81 82 83
};


Paolo Bacchilega's avatar
Paolo Bacchilega committed
84 85
struct _GthImageViewerPagePrivate {
	GthBrowser        *browser;
86
	GSettings         *settings;
87
	GtkWidget         *image_navigator;
88
	GtkWidget         *overview_revealer;
89
	GtkWidget         *overview;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
90 91
	GtkWidget         *viewer;
	GthImagePreloader *preloader;
92
	guint              file_popup_merge_id;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
93 94
	GthImageHistory   *history;
	GthFileData       *file_data;
95
	GFileInfo         *updated_info;
96
	gboolean           active;
97
	gboolean           image_changed;
98
	gboolean           loading_image;
99
	GFile             *last_loaded;
100
	gboolean           can_paste;
101
	guint              update_quality_id;
102
	guint		   update_visibility_id;
103
	GtkWidget         *buttons[N_HEADER_BAR_BUTTONS];
Paolo Bacchilega's avatar
Paolo Bacchilega committed
104
	GtkBuilder        *builder;
105 106
	gboolean           pointer_on_viewer;
	gboolean           pointer_on_overview;
107
	guint              hide_overview_id;
108
	gboolean           apply_icc_profile;
109 110
	GthFileData       *next_file_data[N_FORWARD_PRELOADERS];
	GthFileData       *prev_file_data[N_BACKWARD_PRELOADERS];
111
	gulong             drag_data_get_event;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
112 113 114
};


115 116 117 118 119 120 121 122
G_DEFINE_TYPE_WITH_CODE (GthImageViewerPage,
			 gth_image_viewer_page,
			 G_TYPE_OBJECT,
			 G_ADD_PRIVATE (GthImageViewerPage)
			 G_IMPLEMENT_INTERFACE (GTH_TYPE_VIEWER_PAGE,
						gth_viewer_page_interface_init))


123 124 125 126 127 128 129 130 131 132 133 134
static void
gth_image_viewer_page_file_loaded (GthImageViewerPage *self,
				   gboolean            success)
{
	if (_g_file_equal (self->priv->last_loaded, self->priv->file_data->file))
		return;

	_g_object_unref (self->priv->last_loaded);
	self->priv->last_loaded = g_object_ref (self->priv->file_data->file);

	gth_viewer_page_file_loaded (GTH_VIEWER_PAGE (self),
				     self->priv->file_data,
135
				     self->priv->updated_info,
136 137 138 139
				     success);
}


140
static int
141
get_viewer_size (GthImageViewerPage *self)
142
{
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
	GtkAllocation allocation;
	int           size;

	gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation);
	size = MAX (allocation.width, allocation.height);
	if (size <= 1) {
		int window_width;
		int window_height;
		gtk_window_get_size (GTK_WINDOW (self->priv->browser),
				     &window_width,
				     &window_height);
		size = MAX (window_width, window_height);;
	}

	return size;
}


static int
_gth_image_preloader_get_requested_size_for_next_images (GthImageViewerPage *self)
{
	int requested_size;
165 166 167 168 169 170 171

	requested_size = -1;

	switch (gth_image_viewer_get_zoom_change (GTH_IMAGE_VIEWER (self->priv->viewer))) {
	case GTH_ZOOM_CHANGE_ACTUAL_SIZE:
		requested_size = -1;
		break;
172
	default:
173
		requested_size = (int) floor ((double) get_viewer_size (self) * 0.5);
174 175 176
		break;
	}

177
	return requested_size * gtk_widget_get_scale_factor (GTK_WIDGET (self->priv->viewer));
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
}


static int
_gth_image_preloader_get_requested_size_for_current_image (GthImageViewerPage *self)
{
	int	requested_size;
	double	zoom;

	requested_size = -1;

	switch (gth_image_viewer_get_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer))) {
	case GTH_FIT_NONE:
		zoom = gth_image_viewer_get_zoom (GTH_IMAGE_VIEWER (self->priv->viewer));
		if (zoom < 1.0) {
			int original_width;
			int original_height;

			gth_image_viewer_get_original_size (GTH_IMAGE_VIEWER (self->priv->viewer), &original_width, &original_height);
			requested_size = MAX (original_width * zoom, original_height * zoom);
		}
		else
			requested_size = -1;
		break;

203
	default:
204
		requested_size = get_viewer_size (self);
205 206
		break;
	}
207

208
	return requested_size * gtk_widget_get_scale_factor (GTK_WIDGET (self->priv->viewer));
209 210 211
}


212 213 214 215 216 217 218 219 220 221 222 223 224
/* -- _gth_image_viewer_page_load_with_preloader -- */


typedef struct {
	GthImageViewerPage  *self;
	GthFileData         *file_data;
	int                  requested_size;
	GCancellable        *cancellable;
	GAsyncReadyCallback  callback;
	gpointer	     user_data;
} ProfileData;


225
static void
226
profile_data_free (ProfileData *profile_data)
227
{
228 229 230 231 232
	_g_object_unref (profile_data->cancellable);
	_g_object_unref (profile_data->file_data);
	_g_object_unref (profile_data->self);
	g_free (profile_data);
}
233

234 235 236 237 238 239 240 241 242

static void
_gth_image_viewer_page_load_with_preloader_step2 (GthImageViewerPage  *self,
						  GthFileData         *file_data,
						  int                  requested_size,
						  GCancellable        *cancellable,
						  GAsyncReadyCallback  callback,
						  gpointer	       user_data)
{
243
	g_object_ref (self);
244

245 246 247 248 249 250 251 252 253 254 255 256 257 258
	gth_image_preloader_load (self->priv->preloader,
				  file_data,
				  requested_size,
				  cancellable,
				  callback,
				  user_data,
				  N_FORWARD_PRELOADERS + N_BACKWARD_PRELOADERS,
				  self->priv->next_file_data[0],
				  self->priv->next_file_data[1],
				  self->priv->prev_file_data[0],
				  self->priv->prev_file_data[1]);
}


259 260 261 262 263 264 265 266
static void
profile_ready_cb (GObject      *source_object,
                  GAsyncResult *res,
                  gpointer      user_data)
{
	ProfileData        *profile_data = user_data;
	GthImageViewerPage *self = profile_data->self;

267
	if (self->priv->active && ! self->priv->image_changed && _g_file_equal (self->priv->file_data->file, profile_data->file_data->file)) {
268 269 270 271 272 273
		GthICCProfile *profile;

		profile = gth_color_manager_get_profile_finish (GTH_COLOR_MANAGER (source_object), res, NULL);
		if (profile == NULL)
			profile = _g_object_ref (gth_browser_get_monitor_profile (self->priv->browser));
		gth_image_preloader_set_out_profile (self->priv->preloader, profile);
274

275 276 277 278 279 280 281 282 283
		_gth_image_viewer_page_load_with_preloader_step2 (profile_data->self,
								  profile_data->file_data,
								  profile_data->requested_size,
								  profile_data->cancellable,
								  profile_data->callback,
								  profile_data->user_data);

		_g_object_unref (profile);
	}
284 285 286 287 288 289 290 291 292 293 294 295 296

	profile_data_free (profile_data);
}


static void
_gth_image_viewer_page_load_with_preloader (GthImageViewerPage  *self,
					    GthFileData         *file_data,
					    int                  requested_size,
					    GCancellable        *cancellable,
					    GAsyncReadyCallback  callback,
					    gpointer		 user_data)
{
297
	if ((file_data != NULL) && self->priv->apply_icc_profile) {
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
		char *monitor_name = NULL;

		if (_gtk_window_get_monitor_info (GTK_WINDOW (self->priv->browser), NULL, NULL, &monitor_name)) {
			ProfileData *profile_data;

			profile_data = g_new (ProfileData, 1);
			profile_data->self = g_object_ref (self);
			profile_data->file_data = g_object_ref (file_data);
			profile_data->requested_size = requested_size;
			profile_data->cancellable = _g_object_ref (cancellable);
			profile_data->callback = callback;
			profile_data->user_data = user_data;

			gth_color_manager_get_profile_async (gth_main_get_default_color_manager(),
							     monitor_name,
							     cancellable,
							     profile_ready_cb,
							     profile_data);

			return;
		}
	}

	gth_image_preloader_set_out_profile (self->priv->preloader, NULL);
	_gth_image_viewer_page_load_with_preloader_step2 (self, file_data, requested_size, cancellable, callback, user_data);
}


326 327 328 329 330 331 332 333 334 335
static gboolean
_gth_image_viewer_page_load_with_preloader_finish (GthImageViewerPage  *self)
{
	gboolean active = self->priv->active;
	g_object_unref (self);

	return active;
}


336 337 338 339 340 341 342 343 344 345 346 347
static void
different_quality_ready_cb (GObject		*source_object,
			    GAsyncResult	*result,
			    gpointer	 	 user_data)
{
	GthImageViewerPage *self = user_data;
	GthFileData	   *requested;
	GthImage	   *image;
	int		    requested_size;
	int		    original_width;
	int		    original_height;
	GError		   *error = NULL;
348
	cairo_surface_t    *s1 = NULL;
349 350 351
	cairo_surface_t    *s2;
	int                 w1, h1, w2, h2;
	gboolean            got_better_quality;
352

353 354 355
	if (! _gth_image_viewer_page_load_with_preloader_finish (self))
		return;

356 357 358 359 360 361 362 363 364 365 366 367 368
	if (! gth_image_preloader_load_finish (GTH_IMAGE_PRELOADER (source_object),
					       result,
					       &requested,
					       &image,
					       &requested_size,
					       &original_width,
					       &original_height,
					       &error))
	{
		g_clear_error (&error);
		return;
	}

369
	if (! (self->priv->image_changed && requested == NULL) && ! _g_file_equal (requested->file, self->priv->file_data->file))
370 371 372 373 374
		goto clear_data;

	if (image == NULL)
		goto clear_data;

375 376
	/* check whether the image is of different quality */

377
	s1 = gth_image_get_cairo_surface (image);
378 379 380
	if (s1 == NULL)
		goto clear_data;

381
	s2 = gth_image_viewer_get_current_image (GTH_IMAGE_VIEWER (self->priv->viewer));
382 383 384 385 386 387 388 389 390 391
	if (s2 == NULL) {
		got_better_quality = TRUE;
	}
	else {
		w1 = cairo_image_surface_get_width (s1);
		h1 = cairo_image_surface_get_height (s1);
		w2 = cairo_image_surface_get_width (s2);
		h2 = cairo_image_surface_get_height (s2);
		got_better_quality = ((w1 > w2) || (h1 > h2));
	}
392 393 394 395 396 397 398 399 400

	if (got_better_quality) {
		gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
		gth_image_viewer_set_better_quality (GTH_IMAGE_VIEWER (self->priv->viewer),
						     image,
						     original_width,
						     original_height);
		gth_image_viewer_set_requested_size (GTH_IMAGE_VIEWER (self->priv->viewer), requested_size);
		gtk_widget_queue_draw (self->priv->viewer);
401 402
	}

403 404
clear_data:

405 406
	if (s1 != NULL)
		cairo_surface_destroy (s1);
407 408 409 410 411 412 413 414
	_g_object_unref (requested);
	_g_object_unref (image);
	g_clear_error (&error);

	return;
}


415 416 417 418 419 420 421 422
static gboolean
_g_mime_type_can_load_different_quality (const char *mime_type)
{
	static const char *supported[] = {
		"image/jpeg",
		"image/x-portable-pixmap"
	};

423
	int i;
424 425 426 427
	for (i = 0; i < G_N_ELEMENTS (supported); i++)
		if (g_strcmp0 (mime_type, supported[i]) == 0)
			return TRUE;

428 429 430
	if (_g_mime_type_is_raw (mime_type))
		return TRUE;

431 432 433 434
	return FALSE;
}


435 436 437 438 439 440 441 442 443 444 445 446 447 448
typedef struct {
	GthImageViewerPage *self;
	GthFileData *file_data;
} UpdateQualityData;


static void
update_quality_data_free (UpdateQualityData *data)
{
	_g_object_unref (data->file_data);
	g_free (data);
}


449 450
static gboolean
update_quality_cb (gpointer user_data)
451
{
452 453 454
	UpdateQualityData  *data = user_data;
	GthImageViewerPage *self = data->self;
	gboolean            file_changed;
455

456 457 458 459 460
	if (! _gth_image_viewer_page_load_with_preloader_finish (self)) {
		update_quality_data_free (data);
		return FALSE;
	}

461 462 463
	if (self->priv->update_quality_id != 0) {
		g_source_remove (self->priv->update_quality_id);
		self->priv->update_quality_id = 0;
464
	}
465

466 467 468 469 470 471
	file_changed = ! _g_file_equal_uris (data->file_data->file, self->priv->file_data->file);
	update_quality_data_free (data);

	if (file_changed)
		return FALSE;

472 473 474 475 476 477
	if (! self->priv->active)
		return FALSE;

	if (self->priv->viewer == NULL)
		return FALSE;

478
	if (self->priv->loading_image)
479
		return FALSE;
480

481 482 483
	if (! self->priv->image_changed && ! _g_mime_type_can_load_different_quality (gth_file_data_get_mime_type (self->priv->file_data)))
		return FALSE;

484 485 486 487 488 489
	_gth_image_viewer_page_load_with_preloader (self,
						    self->priv->image_changed ? GTH_MODIFIED_IMAGE : self->priv->file_data,
						    _gth_image_preloader_get_requested_size_for_current_image (self),
						    NULL,
						    different_quality_ready_cb,
						    self);
490 491 492 493 494 495 496 497

	return FALSE;
}


static void
update_image_quality_if_required (GthImageViewerPage *self)
{
498
#ifndef ALWAYS_LOAD_ORIGINAL_SIZE
499 500
	GthImage *image;

501
	if (self->priv->loading_image || gth_sidebar_tool_is_active (GTH_SIDEBAR (gth_browser_get_viewer_sidebar (self->priv->browser))))
502 503
		return;

504 505 506 507
	image = gth_image_viewer_get_image (GTH_IMAGE_VIEWER (self->priv->viewer));
	if ((image != NULL) && (gth_image_get_is_zoomable (image) || gth_image_get_is_animation (image)))
		return;

508 509 510
	if (self->priv->update_quality_id != 0) {
		g_source_remove (self->priv->update_quality_id);
		self->priv->update_quality_id = 0;
511 512
	}

513 514 515 516 517 518
	UpdateQualityData *data;

	data = g_new0 (UpdateQualityData, 1);
	data->self = self;
	data->file_data = _g_object_ref (self->priv->file_data);

519
	_g_object_ref (self);
520 521
	self->priv->update_quality_id = g_timeout_add (UPDATE_QUALITY_DELAY,
						       update_quality_cb,
522
						       data);
523
#endif
524 525 526
}


527 528 529 530 531 532 533 534 535
static gboolean
hide_overview_after_timeout (gpointer data)
{
	GthImageViewerPage *self = data;

	if (self->priv->hide_overview_id != 0)
		g_source_remove (self->priv->hide_overview_id);
	self->priv->hide_overview_id = 0;

536 537
	if (! self->priv->pointer_on_overview)
		gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->overview_revealer), FALSE);
538 539 540 541 542

	return FALSE;
}


543
static gboolean
544
update_overview_visibility_now (gpointer user_data)
545
{
546
	GthImageViewerPage *self;
547
	gboolean            overview_visible;
548

549
	self = GTH_IMAGE_VIEWER_PAGE (user_data);
550

551 552 553 554 555
	if (self->priv->update_visibility_id != 0) {
		g_source_remove (self->priv->update_visibility_id);
		self->priv->update_visibility_id = 0;
	}

Paolo Bacchilega's avatar
Paolo Bacchilega committed
556 557 558
	if (! self->priv->active)
		return FALSE;

559 560
	overview_visible = self->priv->pointer_on_overview || (self->priv->pointer_on_viewer && gth_image_viewer_has_scrollbars (GTH_IMAGE_VIEWER (self->priv->viewer)));
	gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->overview_revealer), overview_visible);
561

562
	if (overview_visible) {
563 564 565 566
		if (self->priv->hide_overview_id != 0)
			g_source_remove (self->priv->hide_overview_id);
		self->priv->hide_overview_id = g_timeout_add_seconds (HIDE_OVERVIEW_TIMEOUT, hide_overview_after_timeout, self);
	}
567 568

	return FALSE;
569 570 571
}


572 573 574
static void
update_overview_visibility (GthImageViewerPage *self)
{
575 576 577
	if (self->priv->update_visibility_id != 0) {
		g_source_remove (self->priv->update_visibility_id);
		self->priv->update_visibility_id = 0;
578 579
	}

580 581 582
	self->priv->update_visibility_id = g_timeout_add (UPDATE_VISIBILITY_DELAY,
							  update_overview_visibility_now,
							  self);
583 584 585
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
#define MIN_ZOOM_LEVEL 0.3
#define MAX_ZOOM_LEVEL 3.0


static void
zoom_scale_value_changed_cb (GtkScale *scale,
			     gpointer  user_data)
{
	GthImageViewerPage *self = user_data;
	double              x, zoom;

	x = gtk_range_get_value (GTK_RANGE (scale));
	zoom = MIN_ZOOM_LEVEL + (x / 100.0 * (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL));
	gth_image_viewer_set_zoom (GTH_IMAGE_VIEWER (self->priv->viewer), zoom);
}


603 604 605 606 607 608 609 610 611 612 613 614 615
static void
zoom_button_toggled_cb (GtkToggleButton *togglebutton,
	                gpointer         user_data)
{
	GthImageViewerPage *self = user_data;

	if (! gtk_toggle_button_get_active (togglebutton))
		return;

	gth_browser_keep_mouse_visible (self->priv->browser, TRUE);
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
616 617 618 619
static void
zoom_popover_closed_cb (GtkPopover *popover,
			gpointer    user_data)
{
620 621 622 623
	GthImageViewerPage *self = user_data;

	gth_browser_keep_mouse_visible (self->priv->browser, FALSE);
	call_when_idle ((DataFunc) gth_viewer_page_focus, self);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
624 625 626 627 628 629
}


#define ZOOM_EQUAL(a,b) (fabs (a - b) < 1e-3)


630
static void
631
update_zoom_info (GthImageViewerPage *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
632 633 634
{
	double  zoom;
	char   *text;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
635 636 637
	double  x;

	/* status bar */
Paolo Bacchilega's avatar
Paolo Bacchilega committed
638 639 640 641 642

	zoom = gth_image_viewer_get_zoom (GTH_IMAGE_VIEWER (self->priv->viewer));
	text = g_strdup_printf ("  %d%%  ", (int) (zoom * 100));
	gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), text);
	g_free (text);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687

	/* zoom menu */

	gboolean    zoom_enabled;
	GthFit      fit_mode;
	GAction    *action;
	const char *state;

	zoom_enabled = gth_image_viewer_get_zoom_enabled (GTH_IMAGE_VIEWER (self->priv->viewer));
	fit_mode = gth_image_viewer_get_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer));

	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "image-zoom", zoom_enabled);

	state = "";
	if (fit_mode == GTH_FIT_SIZE)
		state = "fit";
	else if (fit_mode == GTH_FIT_WIDTH)
		state = "fit-width";
	else if (fit_mode == GTH_FIT_HEIGHT)
		state = "fit-height";
	else if (fit_mode == GTH_FIT_SIZE_IF_LARGER)
		state = "automatic";
	else if (ZOOM_EQUAL (zoom, 0.5))
		state = "50";
	else if (ZOOM_EQUAL (zoom, 1.0))
		state = "100";
	else if (ZOOM_EQUAL (zoom, 2.0))
		state = "200";
	else if (ZOOM_EQUAL (zoom, 3.0))
		state = "300";

	action = g_action_map_lookup_action (G_ACTION_MAP (self->priv->browser), "image-zoom");
	g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (state));

	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "image-zoom-100", ! ZOOM_EQUAL (zoom, 1.0));
	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "image-zoom-fit-if-larger", fit_mode != GTH_FIT_SIZE_IF_LARGER);

	/* zoom menu scale */

	GtkWidget *scale = _gtk_builder_get_widget (self->priv->builder, "zoom_level_scale");

	g_signal_handlers_block_by_data (scale, self);
	x = (zoom - MIN_ZOOM_LEVEL) / (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) * 100.0;
	gtk_range_set_value (GTK_RANGE (scale), CLAMP (x, 0, 100));
	g_signal_handlers_unblock_by_data (scale, self);
688
}
Paolo Bacchilega's avatar
Paolo Bacchilega committed
689

690

691 692 693 694 695 696 697 698 699 700 701
static void
viewer_zoom_changed_cb (GtkWidget          *widget,
			GthImageViewerPage *self)
{
	update_image_quality_if_required (self);
	self->priv->pointer_on_viewer = TRUE;
	update_overview_visibility (self);
	update_zoom_info (self);
}


702 703 704 705
static void
viewer_image_changed_cb (GtkWidget          *widget,
			 GthImageViewerPage *self)
{
706
	gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
707
	update_image_quality_if_required (self);
708
	update_overview_visibility (self);
709
	update_zoom_info (self);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
710 711 712 713
}


static gboolean
714 715 716
viewer_button_press_event_cb (GtkWidget          *widget,
			      GdkEventButton     *event,
			      GthImageViewerPage *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
717
{
718
	return gth_browser_viewer_button_press_cb (self->priv->browser, event);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
719 720 721
}


722 723 724 725 726 727 728 729 730
static gboolean
viewer_popup_menu_cb (GtkWidget          *widget,
		      GthImageViewerPage *self)
{
	gth_browser_file_menu_popup (self->priv->browser, NULL);
	return TRUE;
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
731
static gboolean
732
viewer_scroll_event_cb (GtkWidget 	   *widget,
733 734
		        GdkEventScroll     *event,
		        GthImageViewerPage *self)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
735
{
736
	return gth_browser_viewer_scroll_event_cb (self->priv->browser, event);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
737 738 739
}


740 741 742 743 744 745 746 747 748 749
static gboolean
viewer_image_map_event_cb (GtkWidget          *widget,
			   GdkEvent           *event,
			   GthImageViewerPage *self)
{
	gth_viewer_page_focus (GTH_VIEWER_PAGE (self));
	return FALSE;
}


750 751 752 753 754 755 756 757 758 759 760 761 762 763
static void
clipboard_targets_received_cb (GtkClipboard *clipboard,
			       GdkAtom      *atoms,
                               int           n_atoms,
                               gpointer      user_data)
{
	GthImageViewerPage *self = user_data;
	int                 i;

	self->priv->can_paste = FALSE;
	for (i = 0; ! self->priv->can_paste && (i < n_atoms); i++)
		if (atoms[i] == gdk_atom_intern_static_string ("image/png"))
			self->priv->can_paste = TRUE;

764
	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "paste-image", self->priv->can_paste);
765 766 767 768 769 770 771 772 773 774

	g_object_unref (self);
}


static void
_gth_image_viewer_page_update_paste_command_sensitivity (GthImageViewerPage *self,
							 GtkClipboard       *clipboard)
{
	self->priv->can_paste = FALSE;
775
	gth_window_enable_action (GTH_WINDOW (self->priv->browser), "paste-image", self->priv->can_paste);
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800

	if (clipboard == NULL)
		clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self->priv->viewer), GDK_SELECTION_CLIPBOARD);
	gtk_clipboard_request_targets (clipboard,
				       clipboard_targets_received_cb,
				       g_object_ref (self));
}


static void
clipboard_owner_change_cb (GtkClipboard *clipboard,
                           GdkEvent     *event,
                           gpointer      user_data)
{
	_gth_image_viewer_page_update_paste_command_sensitivity ((GthImageViewerPage *) user_data, clipboard);
}


static void
viewer_realize_cb (GtkWidget *widget,
                   gpointer   user_data)
{
	GthImageViewerPage *self = user_data;
	GtkClipboard       *clipboard;

801
	clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
802 803 804 805 806 807 808 809 810 811 812 813 814 815
	g_signal_connect (clipboard,
	                  "owner_change",
	                  G_CALLBACK (clipboard_owner_change_cb),
	                  self);
}


static void
viewer_unrealize_cb (GtkWidget *widget,
		     gpointer   user_data)
{
	GthImageViewerPage *self = user_data;
	GtkClipboard       *clipboard;

816
	clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
817 818 819 820 821 822
	g_signal_handlers_disconnect_by_func (clipboard,
	                                      G_CALLBACK (clipboard_owner_change_cb),
	                                      self);
}


823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
static gboolean
image_navigator_get_child_position_cb	(GtkOverlay   *overlay,
					 GtkWidget    *widget,
					 GdkRectangle *allocation,
					 gpointer      user_data)
{
	GthImageViewerPage *self = GTH_IMAGE_VIEWER_PAGE (user_data);
	GtkAllocation       main_alloc;
	gboolean            allocation_filled = FALSE;

	gtk_widget_get_allocation (gtk_bin_get_child (GTK_BIN (overlay)), &main_alloc);
	gtk_widget_get_preferred_width (widget, NULL, &allocation->width);
	gtk_widget_get_preferred_height (widget, NULL, &allocation->height);

	if (widget == self->priv->overview_revealer) {
		allocation->x = main_alloc.width - allocation->width - OVERLAY_MARGIN;
		allocation->y = OVERLAY_MARGIN;
840 841
		if (gth_browser_get_is_fullscreen (self->priv->browser))
			allocation->y += gtk_widget_get_allocated_height (gth_browser_get_fullscreen_headerbar (self->priv->browser));
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
		allocation_filled = TRUE;
	}

	return allocation_filled;
}


static gboolean
overview_motion_notify_event_cb (GtkWidget      *widget,
				 GdkEventMotion *event,
				 gpointer        data)
{
	GthImageViewerPage *self = data;

	if (self->priv->hide_overview_id != 0) {
		g_source_remove (self->priv->hide_overview_id);
		self->priv->hide_overview_id = 0;
	}

	self->priv->pointer_on_viewer = TRUE;
	if (widget == self->priv->overview)
		self->priv->pointer_on_overview = TRUE;
	update_overview_visibility (data);

	return FALSE;
}


static gboolean
overview_leave_notify_event_cb (GtkWidget *widget,
				GdkEvent  *event,
				gpointer   data)
{
	GthImageViewerPage *self = data;

	if (widget == self->priv->overview)
878
		self->priv->pointer_on_overview = gth_image_overview_get_scrolling_is_active (GTH_IMAGE_OVERVIEW (self->priv->overview));
879 880 881 882 883

	return FALSE;
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
884
static void
885 886 887
pref_zoom_quality_changed (GSettings *settings,
			   char      *key,
			   gpointer   user_data)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
888 889 890
{
	GthImageViewerPage *self = user_data;

891 892 893
	if (! self->priv->active || (self->priv->viewer == NULL))
		return;

894 895
	gth_image_viewer_set_zoom_quality (GTH_IMAGE_VIEWER (self->priv->viewer),
					   g_settings_get_enum (self->priv->settings, PREF_IMAGE_VIEWER_ZOOM_QUALITY));
896
	gtk_widget_queue_draw (self->priv->viewer);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
897 898 899 900
}


static void
901 902 903
pref_zoom_change_changed (GSettings *settings,
		   	  char      *key,
		   	  gpointer   user_data)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
904 905 906
{
	GthImageViewerPage *self = user_data;

907 908 909
	if (! self->priv->active || (self->priv->viewer == NULL))
		return;

910 911
	gth_image_viewer_set_zoom_change (GTH_IMAGE_VIEWER (self->priv->viewer),
					  g_settings_get_enum (self->priv->settings, PREF_IMAGE_VIEWER_ZOOM_CHANGE));
912
	gtk_widget_queue_draw (self->priv->viewer);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
913 914 915 916
}


static void
917 918 919
pref_reset_scrollbars_changed (GSettings *settings,
		   	       char      *key,
		   	       gpointer   user_data)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
920 921 922
{
	GthImageViewerPage *self = user_data;

923 924 925
	if (! self->priv->active || (self->priv->viewer == NULL))
		return;

926 927
	gth_image_viewer_set_reset_scrollbars (GTH_IMAGE_VIEWER (self->priv->viewer),
					       g_settings_get_boolean (self->priv->settings, PREF_IMAGE_VIEWER_RESET_SCROLLBARS));
Paolo Bacchilega's avatar
Paolo Bacchilega committed
928 929 930
}


931 932 933 934 935
static void
pref_transparency_style_changed (GSettings *settings,
				 char      *key,
				 gpointer   user_data)
{
936 937 938 939
	GthImageViewerPage   *self = user_data;
	GthTransparencyStyle  style;
	GAction              *action;
	const char           *state;
940 941 942 943

	if (! self->priv->active || (self->priv->viewer == NULL))
		return;

944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
	style = g_settings_get_enum (self->priv->settings, PREF_IMAGE_VIEWER_TRANSPARENCY_STYLE);
	state = "";
	switch (style) {
	case GTH_TRANSPARENCY_STYLE_CHECKERED:
		state = "checkered";
		break;
	case GTH_TRANSPARENCY_STYLE_WHITE:
		state = "white";
		break;
	case GTH_TRANSPARENCY_STYLE_GRAY:
		state = "gray";
		break;
	case GTH_TRANSPARENCY_STYLE_BLACK:
		state = "black";
		break;
	}
	action = g_action_map_lookup_action (G_ACTION_MAP (self->priv->browser), "transparency-style");
	if (action != NULL)
		g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (state));

	gth_image_viewer_set_transparency_style (GTH_IMAGE_VIEWER (self->priv->viewer), style);
965 966 967
}


968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
static void
paint_comment_over_image_func (GthImageViewer *image_viewer,
			       cairo_t        *cr,
			       gpointer        user_data)
{
	GthImageViewerPage *self = user_data;
	GthFileData        *file_data = self->priv->file_data;
	GString            *file_info;
	char               *comment;
	const char         *file_date;
	const char         *file_size;
	int                 current_position;
	int                 n_visibles;
	int                 width;
	int                 height;
	GthMetadata        *metadata;
	PangoLayout        *layout;
	PangoAttrList      *attr_list = NULL;
	GError             *error = NULL;
	char               *text;
	static GdkPixbuf   *icon = NULL;
	int                 icon_width;
	int                 icon_height;
	int                 image_width;
	int                 image_height;
993 994
	const int           x_padding = 20;
	const int           y_padding = 20;
995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
	int                 max_text_width;
	PangoRectangle      bounds;
	int                 text_x;
	int                 text_y;
	int                 icon_x;
	int                 icon_y;

	file_info = g_string_new ("");

	comment = gth_file_data_get_attribute_as_string (file_data, "general::description");
	if (comment != NULL) {
		g_string_append_printf (file_info, "<b>%s</b>\n\n", comment);
		g_free (comment);
	}

	metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "general::datetime");
	if (metadata != NULL)
		file_date = gth_metadata_get_formatted (metadata);
	else
		file_date = g_file_info_get_attribute_string (file_data->info, "gth::file::display-mtime");
	file_size = g_file_info_get_attribute_string (file_data->info, "gth::file::display-size");

	gth_browser_get_file_list_info (self->priv->browser, &current_position, &n_visibles);
	gth_image_viewer_get_original_size (GTH_IMAGE_VIEWER (self->priv->viewer), &width, &height);

	g_string_append_printf (file_info,
			        "<small><i>%s - %dx%d (%d%%) - %s</i>\n<tt>%d/%d - %s</tt></small>",
			        file_date,
			        width,
			        height,
			        (int) (gth_image_viewer_get_zoom (GTH_IMAGE_VIEWER (self->priv->viewer)) * 100),
			        file_size,
			        current_position + 1,
			        n_visibles,
				g_file_info_get_attribute_string (file_data->info, "standard::display-name"));

	layout = gtk_widget_create_pango_layout (GTK_WIDGET (self->priv->viewer), NULL);
	pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
	pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);

	if (! pango_parse_markup (file_info->str,
			          -1,
				  0,
				  &attr_list,
				  &text,
				  NULL,
				  &error))
	{
		g_warning ("Failed to set text from markup due to error parsing markup: %s\nThis is the text that caused the error: %s",  error->message, file_info->str);
		g_error_free (error);
		g_object_unref (layout);
		g_string_free (file_info, TRUE);
		return;
	}

	pango_layout_set_attributes (layout, attr_list);
        pango_layout_set_text (layout, text, strlen (text));

        if (icon == NULL) {
        	GIcon *gicon;

1056
        	gicon = g_themed_icon_new ("dialog-information-symbolic");
1057
        	icon = _g_icon_get_pixbuf (gicon, 24, _gtk_widget_get_icon_theme (GTK_WIDGET (image_viewer)));
1058 1059 1060 1061 1062 1063

        	g_object_unref (gicon);
        }
	icon_width = gdk_pixbuf_get_width (icon);
	icon_height = gdk_pixbuf_get_height (icon);

1064 1065
	image_width = gdk_window_get_width (gtk_widget_get_window (self->priv->viewer));
	image_height = gdk_window_get_height (gtk_widget_get_window (self->priv->viewer));
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
	max_text_width = ((image_width * 3 / 4) - icon_width - (x_padding * 3) - (x_padding * 2));

	pango_layout_set_width (layout, max_text_width * PANGO_SCALE);
	pango_layout_get_pixel_extents (layout, NULL, &bounds);

	bounds.width += (2 * x_padding) + (icon_width + x_padding);
	bounds.height = MIN (image_height - icon_height - (y_padding * 2), bounds.height + (2 * y_padding));
	bounds.x = MAX ((image_width - bounds.width) / 2, 0);
	bounds.y = MAX (image_height - bounds.height - (y_padding * 3), 0);

	text_x = bounds.x + x_padding + icon_width + x_padding;
	text_y = bounds.y + y_padding;
	icon_x = bounds.x + x_padding;
	icon_y = bounds.y + (bounds.height - icon_height) / 2;

	cairo_save (cr);

	/* background */

	_cairo_draw_rounded_box (cr, bounds.x, bounds.y, bounds.width, bounds.height, 8.0);
1086
	cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.80);
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
	cairo_fill (cr);
	cairo_set_line_width (cr, 1.0);
	cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
	cairo_stroke (cr);

	/* icon */

	gdk_cairo_set_source_pixbuf (cr, icon, icon_x, icon_y);
	cairo_rectangle (cr, icon_x, icon_y, icon_width, icon_height);
	cairo_fill (cr);

	/* text */

1100
	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
	pango_cairo_update_layout (cr, layout);
	cairo_move_to (cr, text_x, text_y);
	pango_cairo_show_layout (cr, layout);

	cairo_restore (cr);

        g_free (text);
        pango_attr_list_unref (attr_list);
	g_object_unref (layout);
	g_string_free (file_info, TRUE);
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
1114 1115 1116 1117 1118 1119 1120 1121 1122
static void
gth_image_viewer_page_real_activate (GthViewerPage *base,
				     GthBrowser    *browser)
{
	GthImageViewerPage *self;

	self = (GthImageViewerPage*) base;

	self->priv->browser = browser;
1123 1124
	self->priv->active = TRUE;

1125 1126 1127 1128
	g_action_map_add_action_entries (G_ACTION_MAP (browser),
					 actions,
					 G_N_ELEMENTS (actions),
					 browser);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1129

1130 1131
	self->priv->buttons[0] =
			gth_browser_add_header_bar_button (browser,
1132
							   GTH_BROWSER_HEADER_SECTION_VIEWER_ZOOM,
1133
							   "view-zoom-original-symbolic",
1134
							   _("Set to actual size"),
1135 1136
							   "win.image-zoom-100",
							   NULL);
1137
	self->priv->buttons[1] =
1138
			gth_browser_add_header_bar_button (browser,
1139
							   GTH_BROWSER_HEADER_SECTION_VIEWER_ZOOM,
1140
							   "view-zoom-fit-symbolic",
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1141 1142
							   _("Fit to window if larger"),
							   "win.image-zoom-fit-if-larger",
1143
							   NULL);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1144

1145
	self->priv->builder = gtk_builder_new_from_resource ("/org/gnome/gThumb/image_viewer/data/ui/toolbar-zoom-menu.ui");
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1146 1147
	self->priv->buttons[ZOOM_BUTTON] =
			gth_browser_add_header_bar_menu_button (browser,
1148
								GTH_BROWSER_HEADER_SECTION_VIEWER_ZOOM,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1149 1150 1151 1152 1153 1154 1155
								"view-zoom-in-symbolic",
								NULL,
								_gtk_builder_get_widget (self->priv->builder, "zoom_popover"));
	g_signal_connect (_gtk_builder_get_widget (self->priv->builder, "zoom_level_scale"),
			  "value-changed",
			  G_CALLBACK (zoom_scale_value_changed_cb),
			  self);
1156 1157 1158 1159
	g_signal_connect (self->priv->buttons[ZOOM_BUTTON],
			  "toggled",
			  G_CALLBACK (zoom_button_toggled_cb),
			  self);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1160 1161 1162 1163 1164
	g_signal_connect (_gtk_builder_get_widget (self->priv->builder, "zoom_popover"),
			  "closed",
			  G_CALLBACK (zoom_popover_closed_cb),
			  self);

1165
	self->priv->buttons[APPLY_ICC_PROFILE_BUTTON] =
1166
			gth_browser_add_header_bar_toggle_button (browser,
1167
							   	  GTH_BROWSER_HEADER_SECTION_VIEWER_OTHER_COMMANDS,
1168 1169 1170 1171 1172
								  "color-profile",
								  _("Apply the embedded color profile"),
								  "win.apply-icc-profile",
								  NULL);

1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
	self->priv->buttons[TOGGLE_ANIMATION_BUTTON] =
			gth_browser_add_header_bar_toggle_button (browser,
							   	  GTH_BROWSER_HEADER_SECTION_VIEWER_COMMANDS,
								  "media-playback-start-symbolic",
								  _("Play"),
								  "win.toggle-animation",
								  NULL);
	self->priv->buttons[STEP_ANIMATION_BUTTON] =
			gth_browser_add_header_bar_button (browser,
							   GTH_BROWSER_HEADER_SECTION_VIEWER_COMMANDS,
							   "media-skip-forward-symbolic",
							   _("Next frame"),
							   "win.step-animation",
							   NULL);
1187 1188 1189 1190 1191 1192
	self->priv->buttons[TRANSPARENCY_STYLE_BUTTON] =
			gth_browser_add_header_bar_menu_button (browser,
								GTH_BROWSER_HEADER_SECTION_VIEWER_OTHER_COMMANDS,
								"transparency-symbolic",
								_("Transparency"),
								_gtk_builder_get_widget (self->priv->builder, "transparency_popover"));
1193

1194
	gth_window_add_accelerators (GTH_WINDOW (browser), accelerators, G_N_ELEMENTS (accelerators));
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1195 <