gth-browser.c 199 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) 2005-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
 */

#include <config.h>
23
#include <math.h>
Paolo Bacchilega's avatar
Paolo Bacchilega committed
24
#include <gtk/gtk.h>
25
#include <gdk/gdkkeysyms.h>
Paolo Bacchilega's avatar
Paolo Bacchilega committed
26 27 28
#include "dlg-personalize-filters.h"
#include "glib-utils.h"
#include "gtk-utils.h"
29
#include "gth-auto-paned.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
30 31 32 33 34
#include "gth-browser.h"
#include "gth-browser-actions-callbacks.h"
#include "gth-browser-actions-entries.h"
#include "gth-duplicable.h"
#include "gth-enum-types.h"
35
#include "gth-error.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
36 37 38
#include "gth-file-list.h"
#include "gth-file-view.h"
#include "gth-file-selection.h"
39
#include "gth-file-tool.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
40 41 42
#include "gth-filter.h"
#include "gth-filterbar.h"
#include "gth-folder-tree.h"
43
#include "gth-grid-view.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
44
#include "gth-icon-cache.h"
45
#include "gth-info-bar.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
46
#include "gth-image-preloader.h"
47
#include "gth-location-bar.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
48 49 50 51
#include "gth-location-chooser.h"
#include "gth-main.h"
#include "gth-marshal.h"
#include "gth-metadata-provider.h"
52
#include "gth-paned.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
53
#include "gth-preferences.h"
54
#include "gth-progress-dialog.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
55 56
#include "gth-sidebar.h"
#include "gth-statusbar.h"
57
#include "gth-toolbox.h"
58
#include "gth-user-dir.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
59 60
#include "gth-viewer-page.h"
#include "gth-window.h"
61
#include "main.h"
Paolo Bacchilega's avatar
Paolo Bacchilega committed
62 63 64

#define GTH_BROWSER_CALLBACK(f) ((GthBrowserCallback) (f))
#define MAX_HISTORY_LENGTH 15
65
#define LOAD_FILE_DELAY 150
66
#define LOAD_METADATA_DELAY 150
67
#define HIDE_MOUSE_DELAY 1 /* in seconds */
68
#define MOTION_THRESHOLD 0
69
#define UPDATE_SELECTION_DELAY 200
70
#define MIN_SIDEBAR_SIZE 100
71
#define MIN_VIEWER_SIZE 256
72
#define STATUSBAR_SEPARATOR "  ·  "
73 74
#define SHIRNK_WRAP_WIDTH_OFFSET 100
#define SHIRNK_WRAP_HEIGHT_OFFSET 125
75
#define FILE_PROPERTIES_MINIMUM_HEIGHT 100
76
#define HISTORY_FILE "history.xbel"
77
#define OVERLAY_MARGIN 10
78

Paolo Bacchilega's avatar
Paolo Bacchilega committed
79

80 81 82
G_DEFINE_TYPE (GthBrowser, gth_browser, GTH_TYPE_WINDOW)


Paolo Bacchilega's avatar
Paolo Bacchilega committed
83 84 85 86 87
enum {
	LOCATION_READY,
	LAST_SIGNAL
};

88

89 90 91 92 93 94 95 96 97 98
typedef struct {
	gboolean        saved;
	GthBrowserPage  page;
	GFile          *location;
	GFile          *current_file;
	GList          *selected;
	double          vscroll;
} BrowserState;


Paolo Bacchilega's avatar
Paolo Bacchilega committed
99
struct _GthBrowserPrivate {
Paolo Bacchilega's avatar
Paolo Bacchilega committed
100 101
	/* UI staff */

102
	GtkWidget         *infobar;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
103
	GtkWidget         *statusbar;
104 105
	GtkWidget         *browser_right_container;
	GtkWidget         *browser_left_container;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
106 107 108 109
	GtkWidget         *browser_sidebar;
	GtkWidget         *folder_tree;
	GtkWidget         *folder_popup;
	GtkWidget         *file_list_popup;
110
	GtkWidget         *file_popup;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
111 112 113
	GtkWidget         *filterbar;
	GtkWidget         *file_list;
	GtkWidget         *list_extra_widget_container;
114 115
	GtkWidget         *list_info_bar;
	GtkWidget         *location_bar;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
116
	GtkWidget         *file_properties;
117
	GtkWidget         *header_sections[GTH_BROWSER_N_HEADER_SECTIONS];
118 119
	GtkWidget         *browser_status_commands;
	GtkWidget         *viewer_status_commands;
120
	GtkWidget         *menu_button;
121
	GHashTable	  *menu_managers;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
122

123 124
	GtkWidget         *thumbnail_list;

125
	GList             *viewer_pages;
126
	GtkOrientation     viewer_thumbnails_orientation;
127 128
	GtkWidget         *viewer_thumbnails_pane;
	GtkWidget         *viewer_sidebar_pane;
129
	GtkWidget         *viewer_sidebar_alignment;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
130 131 132 133
	GtkWidget         *viewer_container;
	GthViewerPage     *viewer_page;
	GthImagePreloader *image_preloader;

134 135
	GtkWidget         *progress_dialog;

Paolo Bacchilega's avatar
Paolo Bacchilega committed
136 137 138 139 140 141 142
	GHashTable        *named_dialogs;

	/* Browser data */

	gulong             folder_changed_id;
	gulong             file_renamed_id;
	gulong             metadata_changed_id;
143
	gulong             emblems_changed_id;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
144
	gulong             entry_points_changed_id;
145
	gulong             order_changed_id;
146
	GthFileData       *location;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
147 148
	GthFileData       *current_file;
	GthFileSource     *location_source;
149 150
	int                n_visibles;
	int                current_file_position;
151
	GFile             *monitor_location;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
152 153
	gboolean           activity_ref;
	GthIconCache      *menu_icon_cache;
154 155 156 157
	GthFileDataSort   *current_sort_type;
	gboolean           current_sort_inverse;
	GthFileDataSort   *default_sort_type;
	gboolean           default_sort_inverse;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
158 159 160 161 162
	gboolean           show_hidden_files;
	gboolean           fast_file_type;
	gboolean           closing;
	GthTask           *task;
	gulong             task_completed;
163
	gulong             task_progress;
164
	GList             *background_tasks;
165
	gboolean           close_with_task;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
166
	GList             *load_data_queue;
167
	GList             *load_file_data_queue;
168
	guint              load_file_timeout;
169
	guint              load_metadata_timeout;
170
	char              *list_attributes;
171
	gboolean           constructed;
172
	guint              construct_step2_event;
173
	guint              selection_changed_event;
174
	GthFileData       *folder_popup_file_data;
175
	gboolean           properties_on_screen;
176 177
	char              *location_free_space;
	gboolean           recalc_location_free_space;
178
	gboolean           file_properties_on_the_right;
179
	GthSidebarState    viewer_sidebar;
180
	BrowserState       state;
181

182 183 184 185 186 187
	/* settings */

	GSettings         *browser_settings;
	GSettings         *messages_settings;
	GSettings         *desktop_interface_settings;

188
	/* fullscreen */
189

Paolo Bacchilega's avatar
Paolo Bacchilega committed
190
	gboolean           fullscreen;
191
	gboolean	   was_fullscreen;
192
	GtkWidget	  *fullscreen_toolbar;
193 194
	GtkWidget         *next_image_button;
	GtkWidget         *previous_image_button;
195
	GList             *viewer_controls;
196
	GList             *fixed_viewer_controls;
197
	gboolean           pointer_visible;
198 199 200 201
	guint              hide_mouse_timeout;
	guint              motion_signal;
	gdouble            last_mouse_x;
	gdouble            last_mouse_y;
202 203 204 205 206
	struct {
		int      page;
		gboolean viewer_properties;
		gboolean viewer_tools;
		gboolean thumbnail_list;
207
		gboolean browser_properties;
208
	} before_fullscreen;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
209 210 211 212 213

	/* history */

	GList             *history;
	GList             *history_current;
214
	GMenu             *history_menu;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
215 216 217 218 219 220
};


static guint gth_browser_signals[LAST_SIGNAL] = { 0 };


221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
/* -- browser_state -- */


static void
browser_state_init (BrowserState *state)
{
	state->saved = FALSE;
	state->page = 0;
	state->location = NULL;
	state->current_file = NULL;
	state->selected = NULL;
}


static void
browser_state_free (BrowserState *state)
{
	if (! state->saved)
		return;

	_g_object_unref (state->location);
	_g_object_unref (state->current_file);
	g_list_foreach (state->selected, (GFunc) gtk_tree_path_free, NULL);
	g_list_free (state->selected);

	state->location = NULL;
	state->current_file = NULL;
	state->selected = NULL;
	state->saved = FALSE;
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
253 254 255 256 257 258 259
/* -- monitor_event_data -- */


typedef struct {
	int              ref;
	GthFileSource   *file_source;
	GFile           *parent;
260
	int              position;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
	GthMonitorEvent  event;
	GthBrowser      *browser;
	gboolean         update_file_list;
	gboolean         update_folder_tree;
} MonitorEventData;


static MonitorEventData *
monitor_event_data_new (void)
{
	MonitorEventData *monitor_data;

	monitor_data = g_new0 (MonitorEventData, 1);
	monitor_data->ref = 1;

	return monitor_data;
}


G_GNUC_UNUSED
static MonitorEventData *
monitor_event_data_ref (MonitorEventData *monitor_data)
{
	monitor_data->ref++;
	return monitor_data;
}


static void
monitor_event_data_unref (MonitorEventData *monitor_data)
{
	monitor_data->ref--;

	if (monitor_data->ref > 0)
		return;

	g_object_unref (monitor_data->file_source);
	g_object_unref (monitor_data->parent);
	g_free (monitor_data);
}


/* -- gth_browser -- */


306 307
static void
_gth_browser_update_current_file_position (GthBrowser *browser)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
308
{
309
	GthFileStore *file_store;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
310

311 312 313 314
	file_store = gth_browser_get_file_store (browser);
	browser->priv->n_visibles = gth_file_store_n_visibles (file_store);
	browser->priv->current_file_position = -1;

315
	if (browser->priv->current_file != NULL) {
316
		int pos;
317

318
		pos = gth_file_store_get_pos (file_store, browser->priv->current_file->file);
319
		if (pos >= 0)
320
			browser->priv->current_file_position = pos;
321 322 323 324
	}
}


325 326 327 328 329 330 331
static gboolean
_gth_browser_file_tool_is_active (GthBrowser *browser)
{
	return  gth_toolbox_tool_is_active (GTH_TOOLBOX (gth_sidebar_get_toolbox (GTH_SIDEBAR (browser->priv->file_properties))));
}


332 333 334
void
gth_browser_update_title (GthBrowser *browser)
{
335 336 337
	GString    *title;
	const char *name = NULL;
	GList      *emblems = NULL;
338 339 340 341 342 343 344 345 346 347 348 349

	title = g_string_new (NULL);

	switch (gth_window_get_current_page (GTH_WINDOW (browser))) {
	case GTH_BROWSER_PAGE_BROWSER:
		if (browser->priv->location != NULL)
			name = g_file_info_get_display_name (browser->priv->location->info);
		if (name != NULL)
			g_string_append (title, name);
		break;

	case GTH_BROWSER_PAGE_VIEWER:
350 351 352
		if (_gth_browser_file_tool_is_active (browser)) {
			GtkWidget *toolbox;
			GtkWidget *file_tool;
353

354 355 356
			toolbox = gth_sidebar_get_toolbox (GTH_SIDEBAR (browser->priv->file_properties));
			file_tool = gth_toolbox_get_active_tool (GTH_TOOLBOX (toolbox));
			g_string_append (title, gth_file_tool_get_options_title (GTH_FILE_TOOL (file_tool)));
357
		}
358 359 360 361 362 363 364 365 366 367
		else {
			if (browser->priv->current_file != NULL)
				name = g_file_info_get_display_name (browser->priv->current_file->info);
			if (name != NULL)
				g_string_append (title, name);

			if (gth_browser_get_file_modified (browser)) {
				g_string_append (title, " ");
				g_string_append (title, _("[modified]"));
			}
368

369 370
			if (browser->priv->current_file != NULL) {
				GthStringList *string_list;
371

372 373 374 375
				string_list = GTH_STRING_LIST (g_file_info_get_attribute_object (browser->priv->current_file->info, GTH_FILE_ATTRIBUTE_EMBLEMS));
				if (string_list != NULL)
					emblems = _g_string_list_dup (gth_string_list_get_list (string_list));
			}
376
		}
377
		break;
378 379
	}

380 381
	if (title->len == 0)
		g_string_append (title, _("gThumb"));
382

383 384 385
	gth_window_set_title (GTH_WINDOW (browser),
			      title->str,
			      emblems);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
386 387

	g_string_free (title, TRUE);
388
	_g_string_list_free (emblems);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
389 390 391 392 393 394 395 396 397 398
}


void
gth_browser_update_sensitivity (GthBrowser *browser)
{
	GFile    *parent;
	gboolean  parent_available;
	gboolean  viewer_can_save;
	gboolean  modified;
399
	int       n_selected;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
400 401

	if (browser->priv->location != NULL)
402
		parent = g_file_get_parent (browser->priv->location->file);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
403 404 405 406 407 408 409
	else
		parent = NULL;
	parent_available = (parent != NULL);
	_g_object_unref (parent);

	viewer_can_save = (browser->priv->location != NULL) && (browser->priv->viewer_page != NULL) && gth_viewer_page_can_save (GTH_VIEWER_PAGE (browser->priv->viewer_page));
	modified = gth_browser_get_file_modified (browser);
410
	n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
Paolo Bacchilega's avatar
Paolo Bacchilega committed
411

412 413 414
	gth_window_enable_action (GTH_WINDOW (browser), "show-thumbnail-list", gth_window_get_current_page (GTH_WINDOW (browser)) == GTH_BROWSER_PAGE_VIEWER);
	gth_window_enable_action (GTH_WINDOW (browser), "show-sidebar", gth_window_get_current_page (GTH_WINDOW (browser)) == GTH_BROWSER_PAGE_BROWSER);
	/* gth_window_enable_action (GTH_WINDOW (browser), "view-reload", gth_window_get_current_page (GTH_WINDOW (browser)) == GTH_BROWSER_PAGE_BROWSER);*/ /* FIXME add view-reload */
415 416 417 418 419
	gth_window_enable_action (GTH_WINDOW (browser), "save", viewer_can_save && modified);
	gth_window_enable_action (GTH_WINDOW (browser), "save-as", viewer_can_save);
	gth_window_enable_action (GTH_WINDOW (browser), "revert-to-saved", viewer_can_save && modified);
	gth_window_enable_action (GTH_WINDOW (browser), "clear-history", browser->priv->history != NULL);
	gth_window_enable_action (GTH_WINDOW (browser), "go-up", parent_available);
420
	gth_window_enable_action (GTH_WINDOW (browser), "browser-edit-file", n_selected == 1);
421

422 423
	gth_sidebar_update_sensitivity (GTH_SIDEBAR (browser->priv->file_properties));

Paolo Bacchilega's avatar
Paolo Bacchilega committed
424 425 426 427 428 429 430
	if (browser->priv->viewer_page != NULL)
		gth_viewer_page_update_sensitivity (browser->priv->viewer_page);

	gth_hook_invoke ("gth-browser-update-sensitivity", browser);
}


431
void
432
gth_browser_update_extra_widget (GthBrowser *browser)
433
{
434 435 436
	gtk_widget_show (browser->priv->location_bar);
	_gtk_container_remove_children (GTK_CONTAINER (gth_location_bar_get_action_area (GTH_LOCATION_BAR (browser->priv->location_bar))), NULL, NULL);
	gth_location_bar_set_from_file (GTH_LOCATION_BAR (browser->priv->location_bar), browser->priv->location->file);
437
	gth_hook_invoke ("gth-browser-update-extra-widget", browser);
438 439 440
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
441
static void
442 443
_gth_browser_set_location (GthBrowser  *browser,
			   GthFileData *location)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
444
{
445 446
	GtkWidget *location_chooser;

Paolo Bacchilega's avatar
Paolo Bacchilega committed
447 448 449 450 451
	if (location == NULL)
		return;

	if (browser->priv->location != NULL)
		g_object_unref (browser->priv->location);
452
	browser->priv->location = gth_file_data_dup (location);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
453

454
	_gth_browser_update_current_file_position (browser);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
455 456
	gth_browser_update_title (browser);
	gth_browser_update_sensitivity (browser);
457

458
	location_chooser = gth_location_bar_get_chooser (GTH_LOCATION_BAR (browser->priv->location_bar));
459
	g_signal_handlers_block_by_data (location_chooser, browser);
460
	gth_browser_update_extra_widget (browser);
461
	g_signal_handlers_unblock_by_data (location_chooser, browser);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
462 463 464
}


465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
static void
_gth_browser_set_location_from_file (GthBrowser *browser,
				     GFile      *file)
{
	GthFileSource *file_source;
	GthFileData   *file_data;
	GFileInfo     *info;

	file_source = gth_main_get_file_source (file);
	info = gth_file_source_get_file_info (file_source, file, GFILE_DISPLAY_ATTRIBUTES);
	file_data = gth_file_data_new (file, info);
	_gth_browser_set_location (browser, file_data);

	g_object_unref (file_data);
	_g_object_unref (info);
	g_object_unref (file_source);
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
484 485 486
static void
_gth_browser_update_go_sensitivity (GthBrowser *browser)
{
487 488 489 490 491 492 493 494
	g_object_set (g_action_map_lookup_action (G_ACTION_MAP (browser), "go-back"),
		      "enabled",
		      (browser->priv->history_current != NULL) && (browser->priv->history_current->next != NULL),
		      NULL);
	g_object_set (g_action_map_lookup_action (G_ACTION_MAP (browser), "go-forward"),
		      "enabled",
		      (browser->priv->history_current != NULL) && (browser->priv->history_current->prev != NULL),
		      NULL);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
495 496 497
}


498 499
#if 0
static void
500
_gth_browser_history_print (GthBrowser *browser)
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
{
	GList *scan;

	g_print ("history:\n");
	for (scan = browser->priv->history; scan; scan = scan->next) {
		GFile *file = scan->data;
		char  *uri;

		uri = g_file_get_uri (file);
		g_print (" %s%s\n", (browser->priv->history_current == scan) ? "*" : " ", uri);

		g_free (uri);
	}
}
#endif


Paolo Bacchilega's avatar
Paolo Bacchilega committed
518
static void
519
_gth_browser_history_menu (GthBrowser *browser)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
520 521 522
{
	_gth_browser_update_go_sensitivity (browser);

523
	/* Update the history menu model for the headerbar button */
Paolo Bacchilega's avatar
Paolo Bacchilega committed
524

525
	g_menu_remove_all (browser->priv->history_menu);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
526

527
	if (browser->priv->history != NULL) {
528 529
		GList *scan;
		int    i;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
530

531 532 533
		for (i = 0, scan = browser->priv->history;
		     scan;
		     scan = scan->next, i++)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
534
		{
535 536 537
			GFile     *file = scan->data;
			GMenuItem *item;
			char      *target;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
538

539
			item = _g_menu_item_new_for_file (file, NULL);
540 541 542
			target = g_strdup_printf ("%d", i);
			g_menu_item_set_action_and_target (item, "win.go-to-history-position", "s", target);
			g_menu_append_item (browser->priv->history_menu, item);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
543

544 545 546 547
			if (browser->priv->history_current == scan) {
				GAction *action = g_action_map_lookup_action (G_ACTION_MAP (browser), "go-to-history-position");
				g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (target));
			}
Paolo Bacchilega's avatar
Paolo Bacchilega committed
548

549
			g_free (target);
550
			g_object_unref (item);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
551 552 553 554 555 556
		}
	}
}


static void
557 558
_gth_browser_history_add (GthBrowser *browser,
			  GFile      *file)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
559 560 561 562
{
	if (file == NULL)
		return;

563
	if ((browser->priv->history_current == NULL) || ! _g_file_equal_uris (file, browser->priv->history_current->data)) {
564 565
		GList *scan;

566 567 568 569 570 571 572 573 574 575 576
		/* remove all files after the current position */
		for (scan = browser->priv->history; scan && (scan != browser->priv->history_current); /* void */) {
			GList *next = scan->next;

			browser->priv->history = g_list_remove_link (browser->priv->history, scan);
			_g_object_list_unref (scan);

			scan = next;
		}

		/* remove all the occurrences of 'file' from the history */
577
		for (scan = browser->priv->history; scan; /* void */) {
578 579 580
			GList *next = scan->next;
			GFile *file_in_history = scan->data;

581 582
			if (_g_file_equal_uris (file, file_in_history)) {
				browser->priv->history = g_list_remove_link (browser->priv->history, scan);
583 584 585 586 587 588
				_g_object_list_unref (scan);
			}

			scan = next;
		}

Paolo Bacchilega's avatar
Paolo Bacchilega committed
589 590 591 592 593 594
		browser->priv->history = g_list_prepend (browser->priv->history, g_object_ref (file));
		browser->priv->history_current = browser->priv->history;
	}
}


595 596 597
static void
_gth_browser_history_save (GthBrowser *browser)
{
598 599
	GSettings     *privacy_settings;
	gboolean       save_history;
600 601 602 603 604 605
	GBookmarkFile *bookmarks;
	GFile         *file;
	char          *filename;
	GList         *scan;
	int            n;

606 607 608 609
	privacy_settings = _g_settings_new_if_schema_installed ("org.gnome.desktop.privacy");
        save_history = (privacy_settings == NULL) || g_settings_get_boolean (privacy_settings, "remember-recent-files");
        _g_object_unref (privacy_settings);

610 611 612 613
	if (! save_history) {
		file = gth_user_dir_get_file_for_read (GTH_DIR_CONFIG, GTHUMB_DIR, HISTORY_FILE, NULL);
		g_file_delete (file, NULL, NULL);
		g_object_unref (file);
614
		return;
615
	}
616

617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
	bookmarks = g_bookmark_file_new ();
	for (scan = browser->priv->history, n = 0; scan && (n < MAX_HISTORY_LENGTH); scan = scan->next, n++) {
		GFile *location = scan->data;
		char  *uri;

		uri = g_file_get_uri (location);
		_g_bookmark_file_add_uri (bookmarks, uri);

		g_free (uri);
	}
	file = gth_user_dir_get_file_for_write (GTH_DIR_CONFIG, GTHUMB_DIR, HISTORY_FILE, NULL);
	filename = g_file_get_path (file);
	g_bookmark_file_to_file (bookmarks, filename, NULL);

	g_free (filename);
	g_object_unref (file);
	g_bookmark_file_free (bookmarks);
}


static void
_gth_browser_history_load (GthBrowser *browser)
{
640 641
	GSettings     *privacy_settings;
	gboolean       load_history;
642 643 644 645 646 647
	GBookmarkFile *bookmarks;
	GFile         *file;
	char          *filename;

	_g_object_list_unref (browser->priv->history);
	browser->priv->history = NULL;
648 649 650 651 652 653 654 655
	browser->priv->history_current = NULL;

	privacy_settings = _g_settings_new_if_schema_installed ("org.gnome.desktop.privacy");
	load_history = (privacy_settings == NULL) || g_settings_get_boolean (privacy_settings, "remember-recent-files");
	_g_object_unref (privacy_settings);

	if (! load_history)
		return;
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679

	bookmarks = g_bookmark_file_new ();
	file = gth_user_dir_get_file_for_read (GTH_DIR_CONFIG, GTHUMB_DIR, HISTORY_FILE, NULL);
	filename = g_file_get_path (file);
	if (g_bookmark_file_load_from_file (bookmarks, filename, NULL)) {
		char **uris;
		int    i;

		uris = g_bookmark_file_get_uris (bookmarks, NULL);
		for (i = 0; (uris[i] != NULL) && (i < MAX_HISTORY_LENGTH); i++)
			browser->priv->history = g_list_prepend (browser->priv->history, g_file_new_for_uri (uris[i]));
		browser->priv->history = g_list_reverse (browser->priv->history);

		g_strfreev (uris);
	}

	browser->priv->history_current = browser->priv->history;

	g_free (filename);
	g_object_unref (file);
	g_bookmark_file_free (bookmarks);
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
static void
_gth_browser_monitor_entry_points (GthBrowser *browser)
{
	GList *scan;

	for (scan = gth_main_get_all_file_sources (); scan; scan = scan->next) {
		GthFileSource *file_source = scan->data;
		gth_file_source_monitor_entry_points (file_source);
	}
}


static void
_gth_browser_update_entry_point_list (GthBrowser *browser)
{
695 696
	GList *entry_points;
	GFile *root;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712

	entry_points = gth_main_get_all_entry_points ();
	root = g_file_new_for_uri ("gthumb-vfs:///");
	gth_folder_tree_set_children (GTH_FOLDER_TREE (browser->priv->folder_tree), root, entry_points);

	g_object_unref (root);
	_g_object_list_unref (entry_points);
}


static GthTest *
_gth_browser_get_file_filter (GthBrowser *browser)
{
	GthTest *filterbar_test;
	GthTest *test;

713 714 715
	if (browser->priv->filterbar == NULL)
		return NULL;

Paolo Bacchilega's avatar
Paolo Bacchilega committed
716 717 718 719 720 721 722 723 724
	filterbar_test = gth_filterbar_get_test (GTH_FILTERBAR (browser->priv->filterbar));
	test = gth_main_add_general_filter (filterbar_test);

	_g_object_unref (filterbar_test);

	return test;
}


725 726 727 728 729 730 731 732
static gboolean
_gth_browser_get_fast_file_type (GthBrowser *browser,
				 GFile      *file)
{
	gboolean fast_file_type;

	fast_file_type = browser->priv->fast_file_type;

733 734 735 736 737
	/* Force the value to FALSE when browsing a cache or a temporary files
	 * directory.
	 * This is mainly used to browse the Firefox cache without changing the
	 * general preference each time, but can be useful for other caches
	 * as well. */
738 739
	if (g_file_has_uri_scheme (file, "file")) {
		char  *uri;
740
		char  *tmp_uri;
741 742 743 744
		char **uri_v;
		int    i;

		uri = g_file_get_uri (file);
745 746 747 748 749 750 751 752 753 754 755 756 757 758

		tmp_uri = g_filename_to_uri (g_get_tmp_dir (), NULL, NULL);
		if (tmp_uri != NULL) {
			gboolean is_tmp_dir;

			is_tmp_dir = (strcmp (uri, tmp_uri) == 0);
			g_free (tmp_uri);

			if (is_tmp_dir) {
				g_free (uri);
				return FALSE;
			}
		}

759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
		uri_v = g_strsplit (uri, "/", -1);
		for (i = 0; uri_v[i] != NULL; i++)
			if (strstr (uri_v[i], "cache")
			    || strstr (uri_v[i], "CACHE")
			    || strstr (uri_v[i], "Cache"))
			{
				fast_file_type = FALSE;
				break;
			}

		 g_strfreev (uri_v);
		 g_free (uri);
	}

	return fast_file_type;
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
777
static void
778
_update_statusbar_list_info (GthBrowser *browser)
Paolo Bacchilega's avatar
Paolo Bacchilega committed
779
{
780 781 782 783 784 785 786 787 788 789 790 791
	GList   *file_list;
	int      n_total;
	goffset  size_total;
	GList   *scan;
	int      n_selected;
	goffset  size_selected;
	GList   *selected;
	char    *size_total_formatted;
	char    *size_selected_formatted;
	char    *text_total;
	char    *text_selected;
	GString *text;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823

	/* total */

	file_list = gth_file_store_get_visibles (gth_browser_get_file_store (browser));
	n_total = 0;
	size_total = 0;
	for (scan = file_list; scan; scan = scan->next) {
		GthFileData *file_data = scan->data;

		n_total++;
		size_total += g_file_info_get_size (file_data->info);
	}
	_g_object_list_unref (file_list);

	/* selected */

	selected = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
	file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), selected);

	n_selected = 0;
	size_selected = 0;
	for (scan = file_list; scan; scan = scan->next) {
		GthFileData *file_data = scan->data;

		n_selected++;
		size_selected += g_file_info_get_size (file_data->info);
	}
	_g_object_list_unref (file_list);
	_gtk_tree_path_list_free (selected);

	/**/

824 825
	size_total_formatted = g_format_size (size_total);
	size_selected_formatted = g_format_size (size_selected);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
826
	text_total = g_strdup_printf (g_dngettext (NULL, "%d file (%s)", "%d files (%s)", n_total), n_total, size_total_formatted);
827
	text_selected = g_strdup_printf (g_dngettext (NULL, "%d file selected (%s)", "%d files selected (%s)", n_selected), n_selected, size_selected_formatted);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
828

829 830 831 832 833
	text = g_string_new (text_total);
	if (n_selected > 0) {
		g_string_append (text, STATUSBAR_SEPARATOR);
		g_string_append (text, text_selected);
	}
834
	if (browser->priv->location_free_space != NULL) {
835
		g_string_append (text, STATUSBAR_SEPARATOR);
836
		g_string_append (text, browser->priv->location_free_space);
837 838 839 840
	}
	gth_statusbar_set_list_info (GTH_STATUSBAR (browser->priv->statusbar), text->str);

	g_string_free (text, TRUE);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
841 842 843 844 845 846 847
	g_free (text_selected);
	g_free (text_total);
	g_free (size_selected_formatted);
	g_free (size_total_formatted);
}


848 849 850 851 852 853 854 855 856 857 858 859 860
static void
get_free_space_ready_cb (GthFileSource *file_source,
			 guint64        total_size,
			 guint64        free_space,
			 GError        *error,
			 gpointer       data)
{
	GthBrowser *browser = data;
	char       *free_space_text;

	if (error == NULL) {
		char *free_space_formatted;

861
		free_space_formatted = g_format_size (free_space);
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
		free_space_text = g_strdup_printf (_("%s of free space"), free_space_formatted);

		g_free (free_space_formatted);
	}
	else
		free_space_text = NULL;

	g_free (browser->priv->location_free_space);
	browser->priv->location_free_space = NULL;
	if (free_space_text != NULL)
		browser->priv->location_free_space = g_strdup (free_space_text);
	g_free (free_space_text);

	browser->priv->recalc_location_free_space = FALSE;

	_update_statusbar_list_info (browser);
}


881 882 883
static void
_gth_browser_update_statusbar_list_info (GthBrowser *browser)
{
884 885 886 887
	if (browser->priv->recalc_location_free_space
	    && (browser->priv->location_source != NULL)
	    && (browser->priv->location != NULL))
	{
888 889 890 891
		gth_file_source_get_free_space (browser->priv->location_source,
						browser->priv->location->file,
						get_free_space_ready_cb,
						browser);
892 893 894 895 896 897 898 899
	}
	else {
		if ((browser->priv->location_source == NULL)
		    || (browser->priv->location == NULL))
		{
			g_free (browser->priv->location_free_space);
			browser->priv->location_free_space = NULL;
		}
900
		_update_statusbar_list_info (browser);
901
	}
902 903 904
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
905 906
typedef struct {
	GthBrowser    *browser;
907
	GthFileData   *requested_folder;
908
	GFile         *requested_folder_parent;
909
	GFile         *file_to_select;
910 911
	GList         *selected;
	double         vscroll;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
912
	GthAction      action;
913
	gboolean       automatic;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
914 915 916 917 918 919 920 921 922 923 924
	GList         *list;
	GList         *current;
	GFile         *entry_point;
	GthFileSource *file_source;
	GCancellable  *cancellable;
} LoadData;


static LoadData *
load_data_new (GthBrowser *browser,
	       GFile      *location,
925
	       GFile      *file_to_select,
926 927
	       GList      *selected,
	       double      vscroll,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
928
	       GthAction   action,
929
	       gboolean    automatic,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
930 931 932 933 934 935 936
	       GFile      *entry_point)
{
	LoadData *load_data;
	GFile    *file;

	load_data = g_new0 (LoadData, 1);
	load_data->browser = browser;
937 938
	load_data->requested_folder = gth_file_data_new (location, NULL);
	load_data->requested_folder_parent = g_file_get_parent (load_data->requested_folder->file);
939 940
	if (file_to_select != NULL)
		load_data->file_to_select = g_file_dup (file_to_select);
941
	else if (browser->priv->current_file != NULL)
942
		load_data->file_to_select = g_file_dup (browser->priv->current_file->file);
943 944
	load_data->selected = g_list_copy_deep (selected, (GCopyFunc) gtk_tree_path_copy, NULL);
	load_data->vscroll = vscroll;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
945
	load_data->action = action;
946
	load_data->automatic = automatic;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
947 948 949 950 951 952
	load_data->cancellable = g_cancellable_new ();

	if (entry_point == NULL)
		return load_data;

	load_data->entry_point = g_object_ref (entry_point);
953
	load_data->file_source = gth_main_get_file_source (load_data->requested_folder->file);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
954

955
	file = g_object_ref (load_data->requested_folder->file);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979
	load_data->list = g_list_prepend (NULL, g_object_ref (file));
	while (! g_file_equal (load_data->entry_point, file)) {
		GFile *parent;

		parent = g_file_get_parent (file);
		g_object_unref (file);
		file = parent;

		load_data->list = g_list_prepend (load_data->list, g_object_ref (file));
	}
	g_object_unref (file);
	load_data->current = NULL;

	browser->priv->load_data_queue = g_list_prepend (browser->priv->load_data_queue, load_data);

	return load_data;
}


static void
load_data_free (LoadData *data)
{
	data->browser->priv->load_data_queue = g_list_remove (data->browser->priv->load_data_queue, data);

980
	g_object_unref (data->requested_folder);
981
	_g_object_unref (data->requested_folder_parent);
982
	_g_object_unref (data->file_to_select);
983
	g_list_free_full (data->selected, (GDestroyNotify) gtk_tree_path_free);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
984 985 986 987 988 989 990 991
	_g_object_unref (data->file_source);
	_g_object_list_unref (data->list);
	_g_object_unref (data->entry_point);
	g_object_unref (data->cancellable);
	g_free (data);
}


992 993 994 995 996 997 998 999
static void
_gth_browser_load (GthBrowser *browser,
		   GFile      *location,
		   GFile      *file_to_select,
		   GList      *selected,
		   double      vscroll,
		   GthAction   action,
		   gboolean    automatic);
1000 1001


1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
static char *
file_format (const char *format,
	     GFile      *file)
{
	char *name;
	char *s;

	name = g_file_get_parse_name (file);
	s = g_strdup_printf (format, name);

	g_free (name);

	return s;
}


1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
static void
_gth_browser_show_error (GthBrowser *browser,
			 const char *title,
			 GError     *error)
{
	/* _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), title, error); */

	gth_info_bar_set_primary_text (GTH_INFO_BAR (browser->priv->infobar), title);
	gth_info_bar_set_secondary_text (GTH_INFO_BAR (browser->priv->infobar), error->message);

	gtk_orientable_set_orientation (GTK_ORIENTABLE (gtk_info_bar_get_action_area (GTK_INFO_BAR (browser->priv->infobar))), GTK_ORIENTATION_HORIZONTAL);
1029
	gth_info_bar_set_icon_name (GTH_INFO_BAR (browser->priv->infobar), "dialog-error-symbolic", GTK_ICON_SIZE_DIALOG);
1030 1031 1032
	gtk_info_bar_set_message_type (GTK_INFO_BAR (browser->priv->infobar), GTK_MESSAGE_ERROR);
	_gtk_info_bar_clear_action_area (GTK_INFO_BAR (browser->priv->infobar));
	gtk_info_bar_add_buttons (GTK_INFO_BAR (browser->priv->infobar),
1033
				  _GTK_LABEL_CLOSE, GTK_RESPONSE_CLOSE,
1034 1035 1036 1037 1038
				  NULL);
	gtk_widget_show (browser->priv->infobar);
}


Paolo Bacchilega's avatar
Paolo Bacchilega committed
1039
static void
1040
load_data_done (LoadData *load_data,
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1041 1042
		GError   *error)
{
1043
	GthBrowser *browser = load_data->browser;
1044
	char       *title;
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1045 1046 1047 1048

	{
		char *uri;

1049
		uri = g_file_get_uri (load_data->requested_folder->file);
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1050 1051 1052 1053 1054 1055
		debug (DEBUG_INFO, "LOAD READY: %s [%s]\n", uri, (error == NULL ? "Ok" : "Error"));
		performance (DEBUG_INFO, "load done for %s", uri);

		g_free (uri);
	}

1056 1057 1058
	/* moving the "gth-browser-load-location-after" after the
	 * LOCATION_READY signal emition can brake the extensions */

1059 1060 1061 1062 1063 1064
	if ((load_data->action == GTH_ACTION_GO_TO)
	    || (load_data->action == GTH_ACTION_GO_BACK)
	    || (load_data->action == GTH_ACTION_GO_FORWARD)
	    || (load_data->action == GTH_ACTION_GO_UP)
	    || (load_data->action == GTH_ACTION_VIEW))
	{
1065 1066 1067 1068
		if (error == NULL) {
			_g_object_unref (browser->priv->location_source);
			browser->priv->location_source = g_object_ref (load_data->file_source);
		}
1069 1070
		gth_browser_update_extra_widget (browser);
		gth_hook_invoke ("gth-browser-load-location-after", browser, browser->priv->location, error);
1071
	}
Paolo Bacchilega's avatar
Paolo Bacchilega committed
1072

1073 1074 1075 1076 1077 1078 1079
	browser->priv->activity_ref--;
	g_signal_emit (G_OBJECT (browser),
		       gth_browser_signals[LOCATION_READY],
		       0,
		       load_data->requested_folder->file,
		       (error != NULL));

Paolo Bacchilega's avatar
Paolo Bacchilega committed
1080 1081 1082 1083 1084 1085 1086 1087
	if (error == NULL)
		return;

	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		g_error_free (error);
		return;
	}

1088 1089 1090
	if (load_data->automatic) {
		GFile *parent;