1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2008 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <string.h>
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26 #include "glib-utils.h"
27 #include "gth-empty-list.h"
28 #include "gth-file-list.h"
29 #include "gth-file-selection.h"
30 #include "gth-file-store.h"
31 #include "gth-icon-cache.h"
32 #include "gth-preferences.h"
33 #include "gth-thumb-loader.h"
34 #include "gtk-utils.h"
35 
36 
37 #define FLASH_THUMBNAIL_QUEUE_TIMEOUT 100
38 #define UPDATE_THUMBNAILS_AFTER_SCROLL_TIMEOUT 125
39 #define RESTART_LOADING_THUMBS_DELAY 1500
40 #define N_VIEWAHEAD 50
41 #define N_CREATEAHEAD 50000
42 #define EMPTY (N_("(Empty)"))
43 #define CHECK_JOBS_INTERVAL 50
44 #define _FILE_VIEW "file-view"
45 #define _EMPTY_VIEW "empty-view"
46 
47 
48 typedef enum {
49 	GTH_FILE_LIST_OP_TYPE_SET_FILES,
50 	GTH_FILE_LIST_OP_TYPE_CLEAR_FILES,
51 	GTH_FILE_LIST_OP_TYPE_ADD_FILES,
52 	GTH_FILE_LIST_OP_TYPE_UPDATE_FILES,
53 	GTH_FILE_LIST_OP_TYPE_UPDATE_EMBLEMS,
54 	GTH_FILE_LIST_OP_TYPE_DELETE_FILES,
55 	GTH_FILE_LIST_OP_TYPE_SET_FILTER,
56 	GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC,
57 	GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS,
58 	GTH_FILE_LIST_OP_TYPE_RENAME_FILE,
59 	GTH_FILE_LIST_OP_TYPE_MAKE_FILE_VISIBLE,
60 	GTH_FILE_LIST_OP_TYPE_RESTORE_STATE
61 } GthFileListOpType;
62 
63 
64 typedef struct {
65 	GthFileListOpType    type;
66 	GtkTreeModel        *model;
67 	GthTest             *filter;
68 	GList               *file_list; /* GthFileData */
69 	GList               *files; /* GFile */
70 	GthFileDataCompFunc  cmp_func;
71 	gboolean             inverse_sort;
72 	char                *sval;
73 	int                  ival;
74 	GFile               *file;
75 	GthFileData         *file_data;
76 	int                  position;
77 	GList               *selected;
78 	double               vscroll;
79 } GthFileListOp;
80 
81 
82 typedef struct {
83 	int   ref;
84 	guint error : 1;         /* Whether an error occurred loading
85 				  * this file. */
86 	guint thumb_loaded : 1;  /* Whether we have a thumb of this
87 				  * image. */
88 	guint thumb_created : 1; /* Whether a thumb has been
89 				  * created for this image. */
90 	cairo_surface_t *image;
91 } ThumbData;
92 
93 
94 typedef struct {
95 	GthFileList    *file_list;
96 	GthThumbLoader *loader;
97 	GCancellable   *cancellable;
98 	GthFileData    *file_data;
99 	gboolean        update_in_view;
100 	int             pos;
101 	gboolean        started;
102 } ThumbnailJob;
103 
104 
105 typedef enum {
106 	THUMBNAILER_PHASE_INITIALIZE,
107 	THUMBNAILER_PHASE_UPDATE_VISIBLE,
108 	THUMBNAILER_PHASE_UPDATE_DOWNWARD,
109 	THUMBNAILER_PHASE_UPDATE_UPWARD,
110 	THUMBNAILER_PHASE_COMPLETED
111 } ThumbnailerPhase;
112 
113 
114 typedef struct {
115 	ThumbnailerPhase  phase;
116 	int               first_visibile;
117 	int               last_visible;
118 	int               current_pos;
119 	GList            *current_item;
120 } ThumbnailerState;
121 
122 
123 struct _GthFileListPrivate {
124 	GSettings        *settings;
125 	GthFileListMode   type;
126 	GtkAdjustment    *vadj;
127 	GtkWidget        *notebook;
128 	GtkWidget        *view;
129 	GtkWidget        *message;
130 	GtkWidget        *scrolled_window;
131 	GthIconCache     *icon_cache;
132 	GthFileSource    *file_source;
133 	gboolean          load_thumbs;
134 	int               thumb_size;
135 	gboolean          ignore_hidden_thumbs;
136 	GHashTable       *thumb_data;
137 	GthThumbLoader   *thumb_loader;
138 	gboolean          loading_thumbs;
139 	gboolean          dirty;
140 	guint             dirty_event;
141 	guint             restart_thumb_update;
142 	GList            *queue; /* list of GthFileListOp */
143 	GList            *jobs; /* list of ThumbnailJob */
144 	gboolean          cancelling;
145 	guint             update_event;
146 	gboolean          visibility_changed;
147 	GList            *visibles;
148 	ThumbnailerState  thumbnailer_state;
149 };
150 
151 
152 G_DEFINE_TYPE_WITH_CODE (GthFileList,
153 			 gth_file_list,
154 			 GTK_TYPE_BOX,
155 			 G_ADD_PRIVATE (GthFileList))
156 
157 
158 /* OPs */
159 
160 
161 static void _gth_file_list_exec_next_op (GthFileList *file_list);
162 
163 
164 static GthFileListOp *
gth_file_list_op_new(GthFileListOpType op_type)165 gth_file_list_op_new (GthFileListOpType op_type)
166 {
167 	GthFileListOp *op;
168 
169 	op = g_new0 (GthFileListOp, 1);
170 	op->type = op_type;
171 
172 	return op;
173 }
174 
175 
176 static void
gth_file_list_op_free(GthFileListOp * op)177 gth_file_list_op_free (GthFileListOp *op)
178 {
179 	switch (op->type) {
180 	case GTH_FILE_LIST_OP_TYPE_SET_FILES:
181 		_g_object_list_unref (op->file_list);
182 		break;
183 	case GTH_FILE_LIST_OP_TYPE_CLEAR_FILES:
184 		g_free (op->sval);
185 		break;
186 	case GTH_FILE_LIST_OP_TYPE_ADD_FILES:
187 	case GTH_FILE_LIST_OP_TYPE_UPDATE_FILES:
188 	case GTH_FILE_LIST_OP_TYPE_UPDATE_EMBLEMS:
189 		_g_object_list_unref (op->file_list);
190 		break;
191 	case GTH_FILE_LIST_OP_TYPE_DELETE_FILES:
192 		_g_object_list_unref (op->files);
193 		break;
194 	case GTH_FILE_LIST_OP_TYPE_SET_FILTER:
195 		g_object_unref (op->filter);
196 		break;
197 	case GTH_FILE_LIST_OP_TYPE_RENAME_FILE:
198 		g_object_unref (op->file);
199 		g_object_unref (op->file_data);
200 		break;
201 	case GTH_FILE_LIST_OP_TYPE_MAKE_FILE_VISIBLE:
202 		g_object_unref (op->file);
203 		break;
204 	case GTH_FILE_LIST_OP_TYPE_RESTORE_STATE:
205 		g_list_free_full (op->selected, (GDestroyNotify) gtk_tree_path_free);
206 		break;
207 	default:
208 		break;
209 	}
210 	g_free (op);
211 }
212 
213 
214 static void
_gth_file_list_clear_queue(GthFileList * file_list)215 _gth_file_list_clear_queue (GthFileList *file_list)
216 {
217 	if (file_list->priv->dirty_event != 0) {
218 		g_source_remove (file_list->priv->dirty_event);
219 		file_list->priv->dirty_event = 0;
220 		file_list->priv->dirty = FALSE;
221 	}
222 
223 	if (file_list->priv->update_event != 0) {
224 		g_source_remove (file_list->priv->update_event);
225 		file_list->priv->update_event = 0;
226 		file_list->priv->dirty = FALSE;
227 	}
228 
229 	if (file_list->priv->restart_thumb_update != 0) {
230 		g_source_remove (file_list->priv->restart_thumb_update);
231 		file_list->priv->restart_thumb_update = 0;
232 	}
233 
234 	g_list_foreach (file_list->priv->queue, (GFunc) gth_file_list_op_free, NULL);
235 	g_list_free (file_list->priv->queue);
236 	file_list->priv->queue = NULL;
237 }
238 
239 
240 static void
_gth_file_list_remove_op(GthFileList * file_list,GthFileListOpType op_type)241 _gth_file_list_remove_op (GthFileList       *file_list,
242 			  GthFileListOpType  op_type)
243 {
244 	GList *scan;
245 
246 	scan = file_list->priv->queue;
247 	while (scan != NULL) {
248 		GthFileListOp *op = scan->data;
249 
250 		if (op->type != op_type) {
251 			scan = scan->next;
252 			continue;
253 		}
254 
255 		file_list->priv->queue = g_list_remove_link (file_list->priv->queue, scan);
256 		gth_file_list_op_free (op);
257 		g_list_free (scan);
258 
259 		scan = file_list->priv->queue;
260 	}
261 }
262 
263 
264 static void
_gth_file_list_queue_op(GthFileList * file_list,GthFileListOp * op)265 _gth_file_list_queue_op (GthFileList   *file_list,
266 			 GthFileListOp *op)
267 {
268 	if ((op->type == GTH_FILE_LIST_OP_TYPE_SET_FILES) || (op->type == GTH_FILE_LIST_OP_TYPE_CLEAR_FILES))
269 		_gth_file_list_clear_queue (file_list);
270 	if (op->type == GTH_FILE_LIST_OP_TYPE_SET_FILTER)
271 		_gth_file_list_remove_op (file_list, GTH_FILE_LIST_OP_TYPE_SET_FILTER);
272 	file_list->priv->queue = g_list_append (file_list->priv->queue, op);
273 
274 	if (! file_list->priv->loading_thumbs)
275 		_gth_file_list_exec_next_op (file_list);
276 }
277 
278 
279 /* -- gth_file_list -- */
280 
281 
282 static void
gth_file_list_finalize(GObject * object)283 gth_file_list_finalize (GObject *object)
284 {
285 	GthFileList *file_list;
286 
287 	file_list = GTH_FILE_LIST (object);
288 
289 	_gth_file_list_clear_queue (file_list);
290 	_g_object_unref (file_list->priv->thumb_loader);
291 	_g_object_list_unref (file_list->priv->visibles);
292 	g_hash_table_unref (file_list->priv->thumb_data);
293 	if (file_list->priv->icon_cache != NULL)
294 		gth_icon_cache_free (file_list->priv->icon_cache);
295 	g_object_unref (file_list->priv->settings);
296 
297 	G_OBJECT_CLASS (gth_file_list_parent_class)->finalize (object);
298 }
299 
300 
301 static GtkSizeRequestMode
gth_file_list_get_request_mode(GtkWidget * widget)302 gth_file_list_get_request_mode (GtkWidget *widget)
303 {
304 	return GTK_SIZE_REQUEST_CONSTANT_SIZE;
305 }
306 
307 
308 static void
gth_file_list_get_preferred_width(GtkWidget * widget,int * minimum_width,int * natural_width)309 gth_file_list_get_preferred_width (GtkWidget *widget,
310                 		   int       *minimum_width,
311                 		   int       *natural_width)
312 {
313 	GthFileList *file_list = GTH_FILE_LIST (widget);
314 	GtkWidget   *vscrollbar;
315 	const int    border = 1;
316 
317 	gtk_widget_get_preferred_width (file_list->priv->view, minimum_width, natural_width);
318 	if (minimum_width)
319 		*minimum_width += border * 2;
320 	if (natural_width)
321 		*natural_width += border * 2;
322 
323 	vscrollbar = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (file_list->priv->scrolled_window));
324 	if (gtk_widget_get_visible (vscrollbar) || (file_list->priv->type == GTH_FILE_LIST_MODE_V_SIDEBAR)) {
325 		int vscrollbar_minimum_width;
326 		int vscrollbar_natural_width;
327 		int scrollbar_spacing;
328 
329 		gtk_widget_get_preferred_width (vscrollbar, &vscrollbar_minimum_width, &vscrollbar_natural_width);
330 		gtk_widget_style_get (file_list->priv->scrolled_window,
331 				      "scrollbar-spacing", &scrollbar_spacing,
332 				      NULL);
333 
334 		*minimum_width += vscrollbar_minimum_width + scrollbar_spacing;
335 		*natural_width += vscrollbar_natural_width + scrollbar_spacing;
336 	}
337 }
338 
339 
340 static void
gth_file_list_get_preferred_height(GtkWidget * widget,int * minimum_height,int * natural_height)341 gth_file_list_get_preferred_height (GtkWidget *widget,
342                 		    int       *minimum_height,
343                 		    int       *natural_height)
344 {
345 	GthFileList *file_list = GTH_FILE_LIST (widget);
346 	const int    border = 1;
347 
348 	gtk_widget_get_preferred_height (file_list->priv->view, minimum_height, natural_height);
349 	if (minimum_height)
350 		*minimum_height += border * 2;
351 	if (natural_height)
352 		*natural_height += border * 2;
353 }
354 
355 
356 static void
gth_file_list_class_init(GthFileListClass * class)357 gth_file_list_class_init (GthFileListClass *class)
358 {
359 	GObjectClass   *object_class;
360 	GtkWidgetClass *widget_class;
361 
362 	object_class = (GObjectClass*) class;
363 	object_class->finalize = gth_file_list_finalize;
364 
365 	widget_class = (GtkWidgetClass*) class;
366 	widget_class->get_request_mode = gth_file_list_get_request_mode;
367 	widget_class->get_preferred_width = gth_file_list_get_preferred_width;
368 	widget_class->get_preferred_height = gth_file_list_get_preferred_height;
369 }
370 
371 
372 /* -- ThumbData -- */
373 
374 
375 static ThumbData *
thumb_data_new(void)376 thumb_data_new (void)
377 {
378 	ThumbData *data;
379 
380 	data = g_new0 (ThumbData, 1);
381 	data->ref = 1;
382 
383 	return data;
384 }
385 
386 
387 static ThumbData *
thumb_data_ref(ThumbData * data)388 thumb_data_ref (ThumbData *data)
389 {
390 	data->ref++;
391 	return data;
392 }
393 
394 
395 static void
thumb_data_unref(ThumbData * data)396 thumb_data_unref (ThumbData *data)
397 {
398 	data->ref--;
399 	if (data->ref > 0)
400 		return;
401 	cairo_surface_destroy (data->image);
402 	g_free (data);
403 }
404 
405 
406 /* --- */
407 
408 
409 static void
gth_file_list_init(GthFileList * file_list)410 gth_file_list_init (GthFileList *file_list)
411 {
412 	gtk_widget_set_can_focus (GTK_WIDGET (file_list), FALSE);
413 
414 	file_list->priv = gth_file_list_get_instance_private (file_list);
415 	file_list->priv->settings = g_settings_new (GTHUMB_BROWSER_SCHEMA);
416 	file_list->priv->type = GTH_FILE_LIST_MODE_NORMAL;
417 	file_list->priv->vadj = NULL;
418 	file_list->priv->notebook = NULL;
419 	file_list->priv->view = NULL;
420 	file_list->priv->message = NULL;
421 	file_list->priv->scrolled_window = NULL;
422 	file_list->priv->icon_cache = NULL;
423 	file_list->priv->file_source = NULL;
424 	file_list->priv->load_thumbs = TRUE;
425 	file_list->priv->thumb_size = g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_SIZE);
426 	file_list->priv->ignore_hidden_thumbs = FALSE;
427 	file_list->priv->thumb_data = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, (GDestroyNotify) thumb_data_unref);
428 	file_list->priv->thumb_loader = NULL;
429 	file_list->priv->loading_thumbs = FALSE;
430 	file_list->priv->dirty = FALSE;
431 	file_list->priv->dirty_event = 0;
432 	file_list->priv->restart_thumb_update = 0;
433 	file_list->priv->queue = NULL;
434 	file_list->priv->jobs = NULL;
435 	file_list->priv->cancelling = FALSE;
436 	file_list->priv->update_event = 0;
437 	file_list->priv->visibility_changed = FALSE;
438 	file_list->priv->visibles = NULL;
439 	file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_INITIALIZE;
440 }
441 
442 
443 static void _gth_file_list_update_next_thumb (GthFileList *file_list);
444 
445 
446 static gboolean
restart_thumb_update_cb(gpointer data)447 restart_thumb_update_cb (gpointer data)
448 {
449 	GthFileList *file_list = data;
450 
451 	if (file_list->priv->update_event != 0) {
452 		g_source_remove (file_list->priv->update_event);
453 		file_list->priv->update_event = 0;
454 	}
455 
456 	if (file_list->priv->restart_thumb_update != 0) {
457 		g_source_remove (file_list->priv->restart_thumb_update);
458 		file_list->priv->restart_thumb_update = 0;
459 	}
460 
461 	file_list->priv->loading_thumbs = TRUE;
462 	_gth_file_list_update_next_thumb (file_list);
463 
464 	return FALSE;
465 }
466 
467 
468 static void
start_update_next_thumb(GthFileList * file_list)469 start_update_next_thumb (GthFileList *file_list)
470 {
471 	if (file_list->priv->cancelling)
472 		return;
473 
474 	if (file_list->priv->loading_thumbs)
475 		return;
476 
477 	if (! file_list->priv->load_thumbs) {
478 		file_list->priv->loading_thumbs = FALSE;
479 		return;
480 	}
481 
482 	if (file_list->priv->restart_thumb_update != 0)
483 		g_source_remove (file_list->priv->restart_thumb_update);
484 	file_list->priv->restart_thumb_update = g_timeout_add (UPDATE_THUMBNAILS_AFTER_SCROLL_TIMEOUT, restart_thumb_update_cb, file_list);
485 }
486 
487 
488 static void
_gth_file_list_done(GthFileList * file_list)489 _gth_file_list_done (GthFileList *file_list)
490 {
491 	file_list->priv->loading_thumbs = FALSE;
492 }
493 
494 
495 /* ThumbnailJob */
496 
497 
498 static void
thumbnail_job_free(ThumbnailJob * job)499 thumbnail_job_free (ThumbnailJob *job)
500 {
501 	job->file_list->priv->jobs = g_list_remove (job->file_list->priv->jobs, job);
502 	if (job->file_list->priv->jobs == NULL)
503 		_gth_file_list_done (job->file_list);
504 
505 	_g_object_unref (job->file_data);
506 	_g_object_unref (job->cancellable);
507 	_g_object_unref (job->loader);
508 	_g_object_unref (job->file_list);
509 	g_free (job);
510 }
511 
512 
513 static void
thumbnail_job_cancel(ThumbnailJob * job)514 thumbnail_job_cancel (ThumbnailJob *job)
515 {
516 	if (job->started)
517 		g_cancellable_cancel (job->cancellable);
518 	else
519 		thumbnail_job_free (job);
520 }
521 
522 
523 /* --- */
524 
525 
526 static gboolean
vadj_changed_cb(GtkAdjustment * adjustment,gpointer user_data)527 vadj_changed_cb (GtkAdjustment *adjustment,
528 		 gpointer       user_data)
529 {
530 	GthFileList *file_list = user_data;
531 
532 	file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_INITIALIZE;
533 	start_update_next_thumb (GTH_FILE_LIST (user_data));
534 
535 	return FALSE;
536 }
537 
538 
539 static void
file_view_drag_data_get_cb(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,gpointer user_data)540 file_view_drag_data_get_cb (GtkWidget        *widget,
541 			    GdkDragContext   *drag_context,
542 			    GtkSelectionData *data,
543 			    guint             info,
544 			    guint             time,
545 			    gpointer          user_data)
546 {
547 	GthFileList  *file_list = user_data;
548 	GList        *items;
549 	GList        *files;
550 	GList        *scan;
551 	int           n_uris;
552 	char        **uris;
553 	int           i;
554 
555 	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (file_list->priv->view));
556 	files = gth_file_list_get_files (file_list, items);
557 	n_uris = g_list_length (files);
558 	uris = g_new (char *, n_uris + 1);
559 	for (scan = files, i = 0; scan; scan = scan->next, i++) {
560 		GthFileData *file_data = scan->data;
561 		uris[i] = g_file_get_uri (file_data->file);
562 	}
563 	uris[i] = NULL;
564 
565 	gtk_selection_data_set_uris (data, uris);
566 
567 	g_strfreev (uris);
568 	_g_object_list_unref (files);
569 	_gtk_tree_path_list_free (items);
570 }
571 
572 
573 static void
_gth_file_list_update_orientation(GthFileList * file_list)574 _gth_file_list_update_orientation (GthFileList *file_list)
575 {
576 	GtkPolicyType hscrollbar_policy;
577 	GtkPolicyType vscrollbar_policy;
578 
579 	hscrollbar_policy = GTK_POLICY_AUTOMATIC;
580 	vscrollbar_policy = GTK_POLICY_AUTOMATIC;
581 
582 	if (file_list->priv->type == GTH_FILE_LIST_MODE_V_SIDEBAR) {
583 		gtk_orientable_set_orientation (GTK_ORIENTABLE (file_list), GTK_ORIENTATION_VERTICAL);
584 		vscrollbar_policy = GTK_POLICY_ALWAYS;
585 	}
586 	else if (file_list->priv->type == GTH_FILE_LIST_MODE_H_SIDEBAR)
587 		gtk_orientable_set_orientation (GTK_ORIENTABLE (file_list), GTK_ORIENTATION_HORIZONTAL);
588 
589 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (file_list->priv->scrolled_window),
590 					hscrollbar_policy,
591 					vscrollbar_policy);
592 }
593 
594 
595 static void
file_store_visibility_changed_cb(GthFileStore * file_store,GthFileList * file_list)596 file_store_visibility_changed_cb (GthFileStore *file_store,
597 				  GthFileList  *file_list)
598 {
599 	file_list->priv->visibility_changed = TRUE;
600 }
601 
602 
603 static void
file_store_rows_reordered_cb(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer new_order,gpointer user_data)604 file_store_rows_reordered_cb (GtkTreeModel *tree_model,
605 			      GtkTreePath  *path,
606 			      GtkTreeIter  *iter,
607 			      gpointer      new_order,
608 			      gpointer      user_data)
609 {
610 	GthFileList *file_list = user_data;
611 
612 	file_list->priv->visibility_changed = TRUE;
613 }
614 
615 
616 static void
gth_file_list_construct(GthFileList * file_list,GtkWidget * file_view,GthFileListMode list_type,gboolean enable_drag_drop)617 gth_file_list_construct (GthFileList     *file_list,
618 			 GtkWidget       *file_view,
619 			 GthFileListMode  list_type,
620 			 gboolean         enable_drag_drop)
621 {
622 	GthFileStore *model;
623 
624 	file_list->priv->thumb_loader = gth_thumb_loader_new (file_list->priv->thumb_size);
625 	file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), file_list->priv->thumb_size / 2);
626 
627 	/* the main notebook */
628 
629 	file_list->priv->notebook = gtk_stack_new ();
630 	gtk_widget_set_can_focus (file_list->priv->notebook, FALSE);
631 
632 	/* the message pane */
633 
634 	file_list->priv->message = gth_empty_list_new (_(EMPTY));
635 
636 	/* the file view */
637 
638 	file_list->priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
639 	gtk_widget_set_can_focus (file_list->priv->scrolled_window, FALSE);
640 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (file_list->priv->scrolled_window), GTK_SHADOW_ETCHED_IN);
641 
642 	file_list->priv->vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (file_list->priv->scrolled_window));
643 	g_signal_connect (G_OBJECT (file_list->priv->vadj),
644 			  "changed",
645 			  G_CALLBACK (vadj_changed_cb),
646 			  file_list);
647 	g_signal_connect (G_OBJECT (file_list->priv->vadj),
648 			  "value-changed",
649 			  G_CALLBACK (vadj_changed_cb),
650 			  file_list);
651 
652 	file_list->priv->view = file_view;
653 	model = gth_file_store_new ();
654 	gth_file_view_set_model (GTH_FILE_VIEW (file_list->priv->view), GTK_TREE_MODEL (model));
655 	g_object_unref (model);
656 
657 	g_signal_connect (model,
658 			  "visibility-changed",
659 			  G_CALLBACK (file_store_visibility_changed_cb),
660 			  file_list);
661 	g_signal_connect (model,
662 			  "rows-reordered",
663 			  G_CALLBACK (file_store_rows_reordered_cb),
664 			  file_list);
665 	g_signal_connect (G_OBJECT (file_list->priv->view),
666 			  "drag-data-get",
667 			  G_CALLBACK (file_view_drag_data_get_cb),
668 			  file_list);
669 
670 	if (enable_drag_drop)
671 		gth_file_list_enable_drag_source (file_list, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
672 
673 	gth_file_list_set_mode (file_list, list_type);
674 
675 	/* pack the widgets together */
676 
677 	gtk_widget_show (file_list->priv->view);
678 	gtk_container_add (GTK_CONTAINER (file_list->priv->scrolled_window), file_list->priv->view);
679 
680 	gtk_widget_show (file_list->priv->scrolled_window);
681 	gtk_stack_add_named (GTK_STACK (file_list->priv->notebook), file_list->priv->scrolled_window, _FILE_VIEW);
682 
683 	gtk_widget_show (file_list->priv->message);
684 	gtk_stack_add_named (GTK_STACK (file_list->priv->notebook), file_list->priv->message, _EMPTY_VIEW);
685 
686 	gtk_widget_show (file_list->priv->notebook);
687 	gtk_box_pack_start (GTK_BOX (file_list), file_list->priv->notebook, TRUE, TRUE, 0);
688 
689 	gtk_stack_set_visible_child_name (GTK_STACK (file_list->priv->notebook), _EMPTY_VIEW);
690 }
691 
692 
693 GtkWidget *
gth_file_list_new(GtkWidget * file_view,GthFileListMode list_type,gboolean enable_drag_drop)694 gth_file_list_new (GtkWidget       *file_view,
695 		   GthFileListMode  list_type,
696 		   gboolean         enable_drag_drop)
697 {
698 	GtkWidget *widget;
699 
700 	widget = GTK_WIDGET (g_object_new (GTH_TYPE_FILE_LIST, NULL));
701 	gth_file_list_construct (GTH_FILE_LIST (widget), file_view, list_type, enable_drag_drop);
702 
703 	return widget;
704 }
705 
706 
707 void
gth_file_list_set_mode(GthFileList * file_list,GthFileListMode list_type)708 gth_file_list_set_mode (GthFileList     *file_list,
709 			GthFileListMode  list_type)
710 {
711 	g_return_if_fail (GTH_IS_FILE_LIST (file_list));
712 
713 	file_list->priv->type = list_type;
714 
715 	if ((file_list->priv->type == GTH_FILE_LIST_MODE_SELECTOR) || (file_list->priv->type == GTH_FILE_LIST_MODE_NO_SELECTION))
716 		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_NONE);
717 	else if ((file_list->priv->type == GTH_FILE_LIST_MODE_H_SIDEBAR) || (file_list->priv->type == GTH_FILE_LIST_MODE_V_SIDEBAR))
718 		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_SINGLE);
719 	else
720 		gth_file_selection_set_selection_mode (GTH_FILE_SELECTION (file_list->priv->view), GTK_SELECTION_MULTIPLE);
721 
722 	_gth_file_list_update_orientation (file_list);
723 }
724 
725 
726 GthFileListMode
gth_file_list_get_mode(GthFileList * file_list)727 gth_file_list_get_mode (GthFileList *file_list)
728 {
729 	return file_list->priv->type;
730 }
731 
732 
733 typedef struct {
734 	GthFileList *file_list;
735 	DataFunc     done_func;
736 	gpointer     user_data;
737 	guint        check_id;
738 } CancelData;
739 
740 
741 static void
cancel_data_free(CancelData * cancel_data)742 cancel_data_free (CancelData *cancel_data)
743 {
744 	if (cancel_data->check_id != 0)
745 		g_source_remove (cancel_data->check_id);
746 	g_object_unref (cancel_data->file_list);
747 	g_free (cancel_data);
748 }
749 
750 
751 static gboolean
wait_for_jobs_to_finish(gpointer user_data)752 wait_for_jobs_to_finish (gpointer user_data)
753 {
754 	CancelData *cancel_data = user_data;
755 
756 	if (cancel_data->file_list->priv->jobs == NULL) {
757 		if (cancel_data->check_id != 0) {
758 			g_source_remove (cancel_data->check_id);
759 			cancel_data->check_id = 0;
760 		}
761 		cancel_data->file_list->priv->cancelling = FALSE;
762 		if (cancel_data->done_func != NULL)
763 			cancel_data->done_func (cancel_data->user_data);
764 		cancel_data_free (cancel_data);
765 		return FALSE;
766 	}
767 
768 	return TRUE;
769 }
770 
771 
772 static void
_gth_file_list_cancel_jobs(GthFileList * file_list,DataFunc done_func,gpointer user_data)773 _gth_file_list_cancel_jobs (GthFileList *file_list,
774 			    DataFunc     done_func,
775 			    gpointer     user_data)
776 {
777 	CancelData *cancel_data;
778 	GList      *list;
779 	GList      *scan;
780 
781 	cancel_data = g_new0 (CancelData, 1);
782 	cancel_data->file_list = g_object_ref (file_list);
783 	cancel_data->done_func = done_func;
784 	cancel_data->user_data = user_data;
785 
786 	if (file_list->priv->jobs == NULL) {
787 		cancel_data->check_id = g_idle_add (wait_for_jobs_to_finish, cancel_data);
788 		return;
789 	}
790 
791 	list = g_list_copy (file_list->priv->jobs);
792 	for (scan = list; scan; scan = scan->next) {
793 		ThumbnailJob *job = scan->data;
794 		thumbnail_job_cancel (job);
795 	}
796 	g_list_free (list);
797 
798 	cancel_data->check_id = g_timeout_add (CHECK_JOBS_INTERVAL,
799 					       wait_for_jobs_to_finish,
800 					       cancel_data);
801 }
802 
803 
804 void
gth_file_list_cancel(GthFileList * file_list,DataFunc done_func,gpointer user_data)805 gth_file_list_cancel (GthFileList    *file_list,
806 		      DataFunc        done_func,
807 		      gpointer        user_data)
808 {
809 	file_list->priv->cancelling = TRUE;
810 	_gth_file_list_clear_queue (file_list);
811 	_gth_file_list_cancel_jobs (file_list, done_func, user_data);
812 }
813 
814 
815 GthThumbLoader *
gth_file_list_get_thumb_loader(GthFileList * file_list)816 gth_file_list_get_thumb_loader (GthFileList *file_list)
817 {
818 	return file_list->priv->thumb_loader;
819 }
820 
821 
822 static void
gfl_clear_list(GthFileList * file_list,const char * message)823 gfl_clear_list (GthFileList *file_list,
824 		const char  *message)
825 {
826 	GthFileStore *file_store;
827 
828 	gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_list->priv->view));
829 
830 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
831 	gth_file_store_clear (file_store);
832 	g_hash_table_remove_all (file_list->priv->thumb_data);
833 
834 	gth_empty_list_set_text (GTH_EMPTY_LIST (file_list->priv->message), message);
835 	gtk_stack_set_visible_child_name (GTK_STACK (file_list->priv->notebook), _EMPTY_VIEW);
836 }
837 
838 
839 void
gth_file_list_clear(GthFileList * file_list,const char * message)840 gth_file_list_clear (GthFileList *file_list,
841 		     const char  *message)
842 {
843 	GthFileListOp *op;
844 
845 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_CLEAR_FILES);
846 	op->sval = g_strdup (message != NULL ? message : _(EMPTY));
847 	_gth_file_list_queue_op (file_list, op);
848 }
849 
850 
851 static void
_gth_file_list_update_pane(GthFileList * file_list)852 _gth_file_list_update_pane (GthFileList *file_list)
853 {
854 	GthFileStore *file_store;
855 
856 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
857 
858 	if (gth_file_store_n_visibles (file_store) > 0) {
859 		gtk_stack_set_visible_child_name (GTK_STACK (file_list->priv->notebook), _FILE_VIEW);
860 	}
861 	else {
862 		gth_empty_list_set_text (GTH_EMPTY_LIST (file_list->priv->message), _(EMPTY));
863 		gtk_stack_set_visible_child_name (GTK_STACK (file_list->priv->notebook), _EMPTY_VIEW);
864 	}
865 }
866 
867 
868 static void
gfl_add_files(GthFileList * file_list,GList * files,int position)869 gfl_add_files (GthFileList *file_list,
870 	       GList       *files,
871 	       int          position)
872 {
873 	GthFileStore *file_store;
874 	GList        *scan;
875 	char         *cache_base_uri;
876 
877 	performance (DEBUG_INFO, "gfl_add_files start");
878 
879 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
880 
881 	cache_base_uri = g_strconcat (_g_uri_get_home (), "/.thumbnails", NULL);
882 	for (scan = files; scan; scan = scan->next) {
883 		GthFileData     *file_data = scan->data;
884 		char            *uri;
885 		ThumbData       *thumb_data;
886 		GIcon           *icon;
887 		cairo_surface_t *image = NULL;
888 
889 		if (g_file_info_get_file_type (file_data->info) != G_FILE_TYPE_REGULAR)
890 			continue;
891 
892 		if (g_hash_table_lookup (file_list->priv->thumb_data, file_data->file) != NULL)
893 			continue;
894 
895 		uri = g_file_get_uri (file_data->file);
896 
897 		thumb_data = thumb_data_new ();
898 		/* files in the .thumbnails directory are already thumbnails,
899 		 * set them as created. */
900 		thumb_data->thumb_created = _g_uri_is_parent (cache_base_uri, uri);
901 
902 		g_hash_table_insert (file_list->priv->thumb_data,
903 				     g_object_ref (file_data->file),
904 				     thumb_data);
905 
906 		icon = g_file_info_get_symbolic_icon (file_data->info);
907 		image = gth_icon_cache_get_surface (file_list->priv->icon_cache, icon);
908 		gth_file_store_queue_add (file_store,
909 					  file_data,
910 					  image,
911 					  TRUE);
912 
913 		cairo_surface_destroy (image);
914 		g_free (uri);
915 	}
916 	g_free (cache_base_uri);
917 
918 	gth_file_store_exec_add (file_store, position);
919 	_gth_file_list_update_pane (file_list);
920 
921 	performance (DEBUG_INFO, "gfl_add_files end");
922 }
923 
924 
925 void
gth_file_list_add_files(GthFileList * file_list,GList * files,int position)926 gth_file_list_add_files (GthFileList *file_list,
927 			 GList       *files,
928 			 int          position)
929 {
930 	GthFileListOp *op;
931 
932 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_ADD_FILES);
933 	op->file_list = _g_object_list_ref (files);
934 	op->position = position;
935 	_gth_file_list_queue_op (file_list, op);
936 }
937 
938 
939 static void
gfl_delete_files(GthFileList * file_list,GList * files)940 gfl_delete_files (GthFileList *file_list,
941 		  GList       *files)
942 {
943 	GthFileStore *file_store;
944 	GList        *scan;
945 
946 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
947 
948 	for (scan = files; scan; scan = scan->next) {
949 		GFile       *file = scan->data;
950 		GtkTreeIter  iter;
951 
952 		if (g_hash_table_lookup (file_list->priv->thumb_data, file) == NULL)
953 			continue;
954 
955 		g_hash_table_remove (file_list->priv->thumb_data, file);
956 
957 		if (gth_file_store_find (file_store, file, &iter))
958 			gth_file_store_queue_remove (file_store, &iter);
959 	}
960 	gth_file_store_exec_remove (file_store);
961 	_gth_file_list_update_pane (file_list);
962 }
963 
964 
965 void
gth_file_list_delete_files(GthFileList * file_list,GList * files)966 gth_file_list_delete_files (GthFileList *file_list,
967 			    GList       *files)
968 {
969 	GthFileListOp *op;
970 
971 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_DELETE_FILES);
972 	op->files = _g_object_list_ref (files);
973 	_gth_file_list_queue_op (file_list, op);
974 }
975 
976 
977 static void
gfl_update_files(GthFileList * file_list,GList * files)978 gfl_update_files (GthFileList *file_list,
979 		  GList       *files)
980 {
981 	GthFileStore *file_store;
982 	GList        *scan;
983 
984 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
985 	for (scan = files; scan; scan = scan->next) {
986 		GthFileData *file_data = scan->data;
987 		ThumbData   *thumb_data;
988 		GtkTreeIter  iter;
989 
990 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
991 		if (thumb_data == NULL)
992 			continue;
993 
994 		thumb_data->error = FALSE;
995 		thumb_data->thumb_loaded = FALSE;
996 		thumb_data->thumb_created = FALSE;
997 
998 		if (gth_file_store_find (file_store, file_data->file, &iter))
999 			gth_file_store_queue_set (file_store,
1000 						  &iter,
1001 						  GTH_FILE_STORE_FILE_DATA_COLUMN, file_data,
1002 						  -1);
1003 	}
1004 	gth_file_store_exec_set (file_store);
1005 	_gth_file_list_update_pane (file_list);
1006 }
1007 
1008 
1009 void
gth_file_list_update_files(GthFileList * file_list,GList * files)1010 gth_file_list_update_files (GthFileList *file_list,
1011 			    GList       *files)
1012 {
1013 	GthFileListOp *op;
1014 
1015 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_UPDATE_FILES);
1016 	op->file_list = _g_object_list_ref (files);
1017 	_gth_file_list_queue_op (file_list, op);
1018 }
1019 
1020 
1021 static void
gfl_update_emblems(GthFileList * file_list,GList * files)1022 gfl_update_emblems (GthFileList *file_list,
1023 		    GList       *files)
1024 {
1025 	GthFileStore *file_store;
1026 	GList        *scan;
1027 
1028 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1029 	for (scan = files; scan; scan = scan->next) {
1030 		GthFileData *file_data = scan->data;
1031 		ThumbData   *thumb_data;
1032 		GtkTreeIter  iter;
1033 
1034 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1035 		if (thumb_data == NULL)
1036 			continue;
1037 
1038 		if (gth_file_store_find (file_store, file_data->file, &iter))
1039 			gth_file_store_queue_set (file_store,
1040 						  &iter,
1041 						  GTH_FILE_STORE_EMBLEMS_COLUMN, g_file_info_get_attribute_object (file_data->info, GTH_FILE_ATTRIBUTE_EMBLEMS),
1042 						  -1);
1043 	}
1044 	gth_file_store_exec_set (file_store);
1045 }
1046 
1047 
1048 void
gth_file_list_update_emblems(GthFileList * file_list,GList * files)1049 gth_file_list_update_emblems (GthFileList *file_list,
1050 			      GList       *files /* GthFileData */)
1051 {
1052 	GthFileListOp *op;
1053 
1054 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_UPDATE_EMBLEMS);
1055 	op->file_list = _g_object_list_ref (files);
1056 	_gth_file_list_queue_op (file_list, op);
1057 }
1058 
1059 
1060 static void
gfl_rename_file(GthFileList * file_list,GFile * file,GthFileData * file_data)1061 gfl_rename_file (GthFileList *file_list,
1062 		 GFile       *file,
1063 		 GthFileData *file_data)
1064 {
1065 	GthFileStore *file_store;
1066 	GtkTreeIter   iter;
1067 
1068 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1069 	if (gth_file_store_find (file_store, file, &iter)) {
1070 		ThumbData *thumb_data;
1071 
1072 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file);
1073 		g_assert (thumb_data != NULL);
1074 
1075 		g_hash_table_insert (file_list->priv->thumb_data,
1076 				     g_object_ref (file_data->file),
1077 				     thumb_data_ref (thumb_data));
1078 		g_hash_table_remove (file_list->priv->thumb_data, file);
1079 
1080 		gth_file_store_set (file_store,
1081 				    &iter,
1082 				    GTH_FILE_STORE_FILE_DATA_COLUMN, file_data,
1083 				    -1);
1084 	}
1085 	_gth_file_list_update_pane (file_list);
1086 }
1087 
1088 
1089 void
gth_file_list_rename_file(GthFileList * file_list,GFile * file,GthFileData * file_data)1090 gth_file_list_rename_file (GthFileList *file_list,
1091 			   GFile       *file,
1092 			   GthFileData *file_data)
1093 {
1094 	GthFileListOp *op;
1095 
1096 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_RENAME_FILE);
1097 	op->file = g_object_ref (file);
1098 	op->file_data = g_object_ref (file_data);
1099 	_gth_file_list_queue_op (file_list, op);
1100 }
1101 
1102 
1103 static void
gfl_set_files(GthFileList * file_list,GList * files)1104 gfl_set_files (GthFileList *file_list,
1105 	       GList       *files)
1106 {
1107 	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader, g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
1108 	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader, g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
1109 	gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_list->priv->view));
1110 
1111 	gth_file_view_set_vscroll (GTH_FILE_VIEW (file_list->priv->view), 0);
1112 	gth_file_store_clear ((GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view)));
1113 	g_hash_table_remove_all (file_list->priv->thumb_data);
1114 	gfl_add_files (file_list, files, -1);
1115 }
1116 
1117 
1118 void
gth_file_list_set_files(GthFileList * file_list,GList * files)1119 gth_file_list_set_files (GthFileList *file_list,
1120 			 GList       *files)
1121 {
1122 	GthFileListOp *op;
1123 
1124 	if (files != NULL) {
1125 		op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_FILES);
1126 		op->file_list = _g_object_list_ref (files);
1127 		_gth_file_list_queue_op (file_list, op);
1128 	}
1129 	else {
1130 		op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_CLEAR_FILES);
1131 		op->sval = g_strdup (_(EMPTY));
1132 		_gth_file_list_queue_op (file_list, op);
1133 	}
1134 }
1135 
1136 
1137 GList *
gth_file_list_get_files(GthFileList * file_list,GList * items)1138 gth_file_list_get_files (GthFileList *file_list,
1139 			 GList       *items)
1140 {
1141 	GList        *list = NULL;
1142 	GthFileStore *file_store;
1143 	GList        *scan;
1144 
1145 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1146 	for (scan = items; scan; scan = scan->next) {
1147 		GtkTreePath *tree_path = scan->data;
1148 		GtkTreeIter  iter;
1149 		GthFileData *file_data;
1150 
1151 		if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (file_store), &iter, tree_path))
1152 			continue;
1153 		file_data = gth_file_store_get_file (file_store, &iter);
1154 		if (file_data != NULL)
1155 			list = g_list_prepend (list, g_object_ref (file_data));
1156 	}
1157 
1158 	return g_list_reverse (list);
1159 }
1160 
1161 
1162 static void
gfl_set_filter(GthFileList * file_list,GthTest * filter)1163 gfl_set_filter (GthFileList *file_list,
1164 		GthTest     *filter)
1165 {
1166 	GthFileStore *file_store;
1167 
1168 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1169 	if (file_store != NULL)
1170 		gth_file_store_set_filter (file_store, filter);
1171 	_gth_file_list_update_pane (file_list);
1172 }
1173 
1174 
1175 void
gth_file_list_set_filter(GthFileList * file_list,GthTest * filter)1176 gth_file_list_set_filter (GthFileList *file_list,
1177 			  GthTest     *filter)
1178 {
1179 	GthFileListOp *op;
1180 
1181 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_FILTER);
1182 	if (filter != NULL)
1183 		op->filter = g_object_ref (filter);
1184 	else
1185 		op->filter = gth_test_new ();
1186 	_gth_file_list_queue_op (file_list, op);
1187 }
1188 
1189 
1190 static void
gfl_set_sort_func(GthFileList * file_list,GthFileDataCompFunc cmp_func,gboolean inverse_sort)1191 gfl_set_sort_func (GthFileList         *file_list,
1192 		   GthFileDataCompFunc  cmp_func,
1193 		   gboolean             inverse_sort)
1194 {
1195 	GthFileStore *file_store;
1196 
1197 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1198 	if (file_store != NULL)
1199 		gth_file_store_set_sort_func (file_store, cmp_func, inverse_sort);
1200 }
1201 
1202 
1203 void
gth_file_list_set_sort_func(GthFileList * file_list,GthFileDataCompFunc cmp_func,gboolean inverse_sort)1204 gth_file_list_set_sort_func (GthFileList         *file_list,
1205 			     GthFileDataCompFunc  cmp_func,
1206 			     gboolean             inverse_sort)
1207 {
1208 	GthFileListOp *op;
1209 
1210 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC);
1211 	op->cmp_func = cmp_func;
1212 	op->inverse_sort = inverse_sort;
1213 	_gth_file_list_queue_op (file_list, op);
1214 }
1215 
1216 
1217 static void
gfl_enable_thumbs(GthFileList * file_list,gboolean enable)1218 gfl_enable_thumbs (GthFileList *file_list,
1219 		   gboolean     enable)
1220 {
1221 	GthFileStore *file_store;
1222 	GtkTreeIter   iter;
1223 
1224 	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader,
1225 					      g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
1226 	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader,
1227 					    g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
1228 
1229 	file_list->priv->load_thumbs = enable;
1230 
1231 	file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1232 	if (gth_file_store_get_first (file_store, &iter)) {
1233 		do {
1234 			GthFileData     *file_data;
1235 			ThumbData       *thumb_data;
1236 			GIcon           *icon;
1237 			cairo_surface_t *image;
1238 
1239 			file_data = gth_file_store_get_file (file_store, &iter);
1240 
1241 			thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1242 			g_assert (thumb_data != NULL);
1243 			thumb_data->thumb_loaded = FALSE;
1244 
1245 			icon = g_file_info_get_symbolic_icon (file_data->info);
1246 			image = gth_icon_cache_get_surface (file_list->priv->icon_cache, icon);
1247 			gth_file_store_queue_set (file_store,
1248 						  &iter,
1249 						  GTH_FILE_STORE_THUMBNAIL_COLUMN, image,
1250 						  GTH_FILE_STORE_IS_ICON_COLUMN, TRUE,
1251 						  -1);
1252 
1253 			cairo_surface_destroy (image);
1254 		}
1255 		while (gth_file_store_get_next (file_store, &iter));
1256 
1257 		gth_file_store_exec_set (file_store);
1258 	}
1259 
1260 	if (enable)
1261 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_INITIALIZE;
1262 	start_update_next_thumb (file_list);
1263 }
1264 
1265 
1266 void
gth_file_list_enable_thumbs(GthFileList * file_list,gboolean enable)1267 gth_file_list_enable_thumbs (GthFileList *file_list,
1268 			     gboolean     enable)
1269 {
1270 	GthFileListOp *op;
1271 
1272 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS);
1273 	op->ival = enable;
1274 	_gth_file_list_queue_op (file_list, op);
1275 }
1276 
1277 
1278 void
gth_file_list_set_ignore_hidden(GthFileList * file_list,gboolean value)1279 gth_file_list_set_ignore_hidden (GthFileList *file_list,
1280 				 gboolean      value)
1281 {
1282 	file_list->priv->ignore_hidden_thumbs = value;
1283 }
1284 
1285 
1286 void
gth_file_list_set_thumb_size(GthFileList * file_list,int size)1287 gth_file_list_set_thumb_size (GthFileList *file_list,
1288 			      int          size)
1289 {
1290 	file_list->priv->thumb_size = size;
1291 
1292 	gth_thumb_loader_set_requested_size (file_list->priv->thumb_loader, size);
1293 	gth_thumb_loader_set_save_thumbnails (file_list->priv->thumb_loader,  g_settings_get_boolean (file_list->priv->settings, PREF_BROWSER_SAVE_THUMBNAILS));
1294 	gth_thumb_loader_set_max_file_size (file_list->priv->thumb_loader, g_settings_get_int (file_list->priv->settings, PREF_BROWSER_THUMBNAIL_LIMIT));
1295 
1296 	gth_icon_cache_free (file_list->priv->icon_cache);
1297 	file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), size / 2);
1298 	gth_icon_cache_set_fallback (file_list->priv->icon_cache, g_themed_icon_new ("text-x-generic-symbolic"));
1299 
1300 	gth_file_view_set_thumbnail_size (GTH_FILE_VIEW (file_list->priv->view), file_list->priv->thumb_size);
1301 
1302 	_gth_file_list_update_orientation (file_list);
1303 }
1304 
1305 
1306 void
gth_file_list_set_caption(GthFileList * file_list,const char * attributes)1307 gth_file_list_set_caption (GthFileList *file_list,
1308 			   const char  *attributes)
1309 {
1310 	gth_file_view_set_caption (GTH_FILE_VIEW (file_list->priv->view), attributes);
1311 }
1312 
1313 
1314 static void
gfl_make_file_visible(GthFileList * file_list,GFile * file)1315 gfl_make_file_visible (GthFileList *file_list,
1316 		       GFile       *file)
1317 {
1318 	int pos;
1319 
1320 	pos = gth_file_store_get_pos ((GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view)), file);
1321 	if (pos >= 0) {
1322 		gth_file_selection_unselect_all (GTH_FILE_SELECTION (file_list->priv->view));
1323 		gth_file_selection_select (GTH_FILE_SELECTION (file_list->priv->view), pos);
1324 		gth_file_view_set_cursor (GTH_FILE_VIEW (file_list->priv->view), pos);
1325 	}
1326 }
1327 
1328 
1329 void
gth_file_list_make_file_visible(GthFileList * file_list,GFile * file)1330 gth_file_list_make_file_visible (GthFileList *file_list,
1331 				 GFile       *file)
1332 {
1333 	GthFileListOp *op;
1334 
1335 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_MAKE_FILE_VISIBLE);
1336 	op->file = g_object_ref (file);
1337 	_gth_file_list_queue_op (file_list, op);
1338 }
1339 
1340 
1341 static void
gfl_restore_state(GthFileList * file_list,GList * selected,double vscroll)1342 gfl_restore_state (GthFileList *file_list,
1343 		   GList       *selected,
1344 		   double       vscroll)
1345 {
1346 	GList *scan;
1347 
1348 	for (scan = selected; scan; scan = scan->next) {
1349 		GtkTreePath *path = scan->data;
1350 		int          pos;
1351 
1352 		pos = gtk_tree_path_get_indices (path)[0];
1353 		gth_file_selection_select (GTH_FILE_SELECTION (file_list->priv->view), pos);
1354 	}
1355 
1356 	gth_file_view_set_vscroll (GTH_FILE_VIEW (file_list->priv->view), vscroll);
1357 }
1358 
1359 
1360 void
gth_file_list_restore_state(GthFileList * file_list,GList * selected,double vscroll)1361 gth_file_list_restore_state (GthFileList *file_list,
1362 			     GList       *selected,
1363 			     double       vscroll)
1364 {
1365 	GthFileListOp *op;
1366 
1367 	op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_RESTORE_STATE);
1368 	op->selected = g_list_copy_deep (selected, (GCopyFunc) gtk_tree_path_copy, NULL);
1369 	op->vscroll = vscroll;
1370 	_gth_file_list_queue_op (file_list, op);
1371 }
1372 
1373 
1374 GtkWidget *
gth_file_list_get_view(GthFileList * file_list)1375 gth_file_list_get_view (GthFileList *file_list)
1376 {
1377 	return file_list->priv->view;
1378 }
1379 
1380 
1381 GtkWidget *
gth_file_list_get_empty_view(GthFileList * file_list)1382 gth_file_list_get_empty_view (GthFileList *file_list)
1383 {
1384 	return file_list->priv->message;
1385 }
1386 
1387 
1388 GtkAdjustment *
gth_file_list_get_vadjustment(GthFileList * file_list)1389 gth_file_list_get_vadjustment (GthFileList *file_list)
1390 {
1391 	return file_list->priv->vadj;
1392 }
1393 
1394 
1395 /* thumbs */
1396 
1397 
1398 static void
flash_queue(GthFileList * file_list)1399 flash_queue (GthFileList *file_list)
1400 {
1401 	if (file_list->priv->dirty) {
1402 		GthFileStore *file_store;
1403 
1404 		file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1405 		gth_file_store_exec_set (file_store);
1406 		file_list->priv->dirty = FALSE;
1407 	}
1408 
1409 	if (file_list->priv->dirty_event != 0) {
1410 		g_source_remove (file_list->priv->dirty_event);
1411 		file_list->priv->dirty_event = 0;
1412 	}
1413 }
1414 
1415 
1416 static gboolean
flash_queue_cb(gpointer data)1417 flash_queue_cb (gpointer data)
1418 {
1419 	flash_queue ((GthFileList *) data);
1420 	return FALSE;
1421 }
1422 
1423 
1424 static void
queue_flash_updates(GthFileList * file_list)1425 queue_flash_updates (GthFileList *file_list)
1426 {
1427 	file_list->priv->dirty = TRUE;
1428 	if (file_list->priv->dirty_event == 0)
1429 		file_list->priv->dirty_event = g_timeout_add (FLASH_THUMBNAIL_QUEUE_TIMEOUT, flash_queue_cb, file_list);
1430 }
1431 
1432 
1433 static gboolean
get_file_data_iter_with_suggested_pos(GthFileStore * file_store,GthFileData * file_data,int try_pos,GtkTreeIter * iter_p)1434 get_file_data_iter_with_suggested_pos (GthFileStore *file_store,
1435 				       GthFileData  *file_data,
1436 				       int           try_pos,
1437 				       GtkTreeIter  *iter_p)
1438 {
1439 	if (gth_file_store_get_nth_visible (file_store, try_pos, iter_p)) {
1440 		GthFileData *nth_file_data;
1441 
1442 		nth_file_data = gth_file_store_get_file (file_store, iter_p);
1443 		if (g_file_equal (file_data->file, nth_file_data->file))
1444 			return TRUE;
1445 
1446 		if (gth_file_store_find (file_store, file_data->file, iter_p))
1447 			return TRUE;
1448 	}
1449 
1450 	return FALSE;
1451 }
1452 
1453 
1454 static void
update_thumb_in_file_view(GthFileList * file_list,GthFileData * file_data,int try_pos)1455 update_thumb_in_file_view (GthFileList *file_list,
1456 		    	   GthFileData *file_data,
1457 			   int          try_pos)
1458 {
1459 	GthFileStore *file_store;
1460 	GtkTreeIter   iter;
1461 	ThumbData    *thumb_data;
1462 
1463 	file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1464 	if (! get_file_data_iter_with_suggested_pos (file_store, file_data, try_pos, &iter))
1465 		return;
1466 
1467 	thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1468 	if (thumb_data == NULL)
1469 		return;
1470 
1471 	if (thumb_data->image == NULL)
1472 		return;
1473 
1474 	gth_file_store_queue_set (file_store,
1475 				  &iter,
1476 				  GTH_FILE_STORE_THUMBNAIL_COLUMN, thumb_data->image,
1477 				  GTH_FILE_STORE_IS_ICON_COLUMN, FALSE,
1478 				  -1);
1479 	queue_flash_updates (file_list);
1480 }
1481 
1482 
1483 static void
set_mime_type_icon(GthFileList * file_list,GthFileData * file_data,int try_pos)1484 set_mime_type_icon (GthFileList *file_list,
1485 		    GthFileData *file_data,
1486 		    int          try_pos)
1487 {
1488 	GthFileStore    *file_store;
1489 	GtkTreeIter      iter;
1490 	GIcon           *icon;
1491 	cairo_surface_t *image;
1492 
1493 	file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1494 	if (! get_file_data_iter_with_suggested_pos (file_store, file_data, try_pos, &iter))
1495 		return;
1496 
1497 	icon = g_file_info_get_symbolic_icon (file_data->info);
1498 	image = gth_icon_cache_get_surface (file_list->priv->icon_cache, icon);
1499 	gth_file_store_queue_set (file_store,
1500 				  &iter,
1501 				  GTH_FILE_STORE_THUMBNAIL_COLUMN, image,
1502 				  GTH_FILE_STORE_IS_ICON_COLUMN, TRUE,
1503 				  -1);
1504 	queue_flash_updates (file_list);
1505 
1506 	cairo_surface_destroy (image);
1507 }
1508 
1509 
1510 static void
thumbnail_job_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1511 thumbnail_job_ready_cb (GObject      *source_object,
1512 		        GAsyncResult *result,
1513 		        gpointer      user_data)
1514 {
1515 	ThumbnailJob    *job = user_data;
1516 	GthFileList     *file_list = job->file_list;
1517 	gboolean         success;
1518 	cairo_surface_t *image = NULL;
1519 	GError          *error = NULL;
1520 	ThumbData       *thumb_data;
1521 
1522 	success = gth_thumb_loader_load_finish (GTH_THUMB_LOADER (source_object),
1523 						result,
1524 						&image,
1525 						&error);
1526 	job->started = FALSE;
1527 
1528 	if ((! success && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1529 	    || file_list->priv->cancelling)
1530 	{
1531 		cairo_surface_destroy (image);
1532 		thumbnail_job_free (job);
1533 		return;
1534 	}
1535 
1536 	thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, job->file_data->file);
1537 	if (thumb_data == NULL) {
1538 		cairo_surface_destroy (image);
1539 		thumbnail_job_free (job);
1540 		_gth_file_list_update_next_thumb (file_list);
1541 		return;
1542 	}
1543 
1544 	cairo_surface_destroy (thumb_data->image);
1545 	thumb_data->image = NULL;
1546 
1547 	if (! success) {
1548 		thumb_data->thumb_created = FALSE;
1549 		thumb_data->thumb_loaded = FALSE;
1550 		if (job->update_in_view)
1551 			set_mime_type_icon (file_list, job->file_data, job->pos);
1552 
1553 		thumb_data->error = TRUE;
1554 	}
1555 	else {
1556 		thumb_data->image = cairo_surface_reference (image);
1557 		thumb_data->thumb_created = TRUE;
1558 		thumb_data->error = FALSE;
1559 		if (job->update_in_view) {
1560 			thumb_data->thumb_loaded = TRUE;
1561 			update_thumb_in_file_view (file_list, job->file_data, job->pos);
1562 		}
1563 	}
1564 
1565 	cairo_surface_destroy (image);
1566 	thumbnail_job_free (job);
1567 
1568 	_gth_file_list_update_next_thumb (file_list);
1569 }
1570 
1571 
1572 static void
set_loading_icon(GthFileList * file_list,GthFileData * file_data,int try_pos)1573 set_loading_icon (GthFileList *file_list,
1574 		  GthFileData *file_data,
1575 		  int          try_pos)
1576 {
1577 	GthFileStore    *file_store;
1578 	GtkTreeIter      iter;
1579 	GIcon           *icon;
1580 	cairo_surface_t *image;
1581 
1582 	file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
1583 	if (! get_file_data_iter_with_suggested_pos (file_store, file_data, try_pos, &iter))
1584 		return;
1585 
1586 	icon = g_themed_icon_new ("content-loading-symbolic");
1587 	image = gth_icon_cache_get_surface (file_list->priv->icon_cache, icon);
1588 	gth_file_store_queue_set (file_store,
1589 				  &iter,
1590 				  GTH_FILE_STORE_THUMBNAIL_COLUMN, image,
1591 				  GTH_FILE_STORE_IS_ICON_COLUMN, TRUE,
1592 				  -1);
1593 	queue_flash_updates (file_list);
1594 
1595 	cairo_surface_destroy (image);
1596 	g_object_unref (icon);
1597 }
1598 
1599 
1600 static gboolean
start_thumbnail_job(gpointer user_data)1601 start_thumbnail_job (gpointer user_data)
1602 {
1603 	ThumbnailJob *job = user_data;
1604 	GthFileList  *file_list = job->file_list;
1605 
1606 	if (file_list->priv->update_event != 0) {
1607 		g_source_remove (file_list->priv->update_event);
1608 		file_list->priv->update_event = 0;
1609 	}
1610 
1611 	job->started = TRUE;
1612 	gth_thumb_loader_load (job->loader,
1613 			       job->file_data,
1614 			       job->cancellable,
1615 			       thumbnail_job_ready_cb,
1616 			       job);
1617 
1618 	return FALSE;
1619 }
1620 
1621 
1622 static void
_gth_file_list_update_thumb(GthFileList * file_list,ThumbnailJob * job)1623 _gth_file_list_update_thumb (GthFileList  *file_list,
1624 			     ThumbnailJob *job)
1625 {
1626 	GList *list;
1627 	GList *scan;
1628 
1629 	if (file_list->priv->update_event != 0) {
1630 		g_source_remove (file_list->priv->update_event);
1631 		file_list->priv->update_event = 0;
1632 	}
1633 
1634 	if (! job->update_in_view) {
1635 		ThumbData *thumb_data;
1636 
1637 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, job->file_data->file);
1638 
1639 		if (gth_thumb_loader_has_valid_thumbnail (file_list->priv->thumb_loader, job->file_data)) {
1640 			thumb_data->thumb_created = TRUE;
1641 			thumb_data->error = FALSE;
1642 			thumbnail_job_free (job);
1643 			job = NULL;
1644 		}
1645 		else if (gth_thumb_loader_has_failed_thumbnail (file_list->priv->thumb_loader, job->file_data)) {
1646 			thumb_data->thumb_created = TRUE;
1647 			thumb_data->error = TRUE;
1648 			thumbnail_job_free (job);
1649 			job = NULL;
1650 		}
1651 
1652 		if (job == NULL) {
1653 			file_list->priv->update_event = g_idle_add (restart_thumb_update_cb, file_list);
1654 			return;
1655 		}
1656 	}
1657 
1658 	list = g_list_copy (file_list->priv->jobs);
1659 	for (scan = list; scan; scan = scan->next) {
1660 		ThumbnailJob *other_job = scan->data;
1661 		thumbnail_job_cancel (other_job);
1662 	}
1663 	g_list_free (list);
1664 
1665 	file_list->priv->jobs = g_list_prepend (file_list->priv->jobs, job);
1666 
1667 	if (job->update_in_view)
1668 		set_loading_icon (job->file_list, job->file_data, job->pos);
1669 	file_list->priv->update_event = g_idle_add (start_thumbnail_job, job);
1670 }
1671 
1672 
1673 static void
_gth_file_list_thumbs_completed(GthFileList * file_list)1674 _gth_file_list_thumbs_completed (GthFileList *file_list)
1675 {
1676 	flash_queue (file_list);
1677 	_gth_file_list_done (file_list);
1678 }
1679 
1680 
1681 static gboolean
update_thumbs_stopped(gpointer callback_data)1682 update_thumbs_stopped (gpointer callback_data)
1683 {
1684 	GthFileList *file_list = callback_data;
1685 
1686 	if (file_list->priv->update_event != 0) {
1687 		g_source_remove (file_list->priv->update_event);
1688 		file_list->priv->update_event = 0;
1689 	}
1690 
1691 	file_list->priv->loading_thumbs = FALSE;
1692 	_gth_file_list_exec_next_op (file_list);
1693 
1694 	return FALSE;
1695 }
1696 
1697 
1698 static gboolean
can_create_file_thumbnail(GthFileData * file_data,ThumbData * thumb_data,GTimeVal * current_time,gboolean * young_file_found)1699 can_create_file_thumbnail (GthFileData *file_data,
1700 			   ThumbData   *thumb_data,
1701 			   GTimeVal    *current_time,
1702 			   gboolean    *young_file_found)
1703 {
1704 
1705 	time_t     time_diff;
1706 	gboolean   young_file;
1707 
1708 	/* Check for files that are exactly 0 or 1 seconds old; they may still be changing. */
1709 	time_diff = current_time->tv_sec - gth_file_data_get_mtime (file_data);
1710 	young_file = (time_diff <= 1) && (time_diff >= 0);
1711 
1712 	if (young_file)
1713 		*young_file_found = TRUE;
1714 
1715 	return ! thumb_data->error && ! young_file;
1716 }
1717 
1718 
1719 static GList *
_gth_file_list_get_visibles(GthFileList * file_list)1720 _gth_file_list_get_visibles (GthFileList *file_list)
1721 {
1722 	if (file_list->priv->visibility_changed) {
1723 		_g_object_list_unref (file_list->priv->visibles);
1724 		file_list->priv->visibles = gth_file_store_get_visibles ((GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view)));
1725 		file_list->priv->visibility_changed = FALSE;
1726 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_INITIALIZE;
1727 	}
1728 
1729 	return file_list->priv->visibles;
1730 }
1731 
1732 
1733 static gboolean
_gth_file_list_thumbnailer_iterate(GthFileList * file_list,int * new_pos,GTimeVal * current_time,gboolean * young_file_found)1734 _gth_file_list_thumbnailer_iterate (GthFileList *file_list,
1735 				    int         *new_pos,
1736 				    GTimeVal    *current_time,
1737 				    gboolean    *young_file_found)
1738 {
1739 	gboolean     iterate_again = TRUE;
1740 	GList       *list;
1741 	GList       *scan;
1742 	int          pos;
1743 	GthFileData *file_data;
1744 	ThumbData   *thumb_data;
1745 
1746 	list = _gth_file_list_get_visibles (file_list);
1747 
1748 	switch (file_list->priv->thumbnailer_state.phase) {
1749 	case THUMBNAILER_PHASE_INITIALIZE:
1750 		file_list->priv->thumbnailer_state.first_visibile = gth_file_view_get_first_visible (GTH_FILE_VIEW (file_list->priv->view));
1751 		file_list->priv->thumbnailer_state.last_visible = gth_file_view_get_last_visible (GTH_FILE_VIEW (file_list->priv->view));
1752 
1753 		/* pass to the 'update visible files' phase. */
1754 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_UPDATE_VISIBLE;
1755 		file_list->priv->thumbnailer_state.current_pos = file_list->priv->thumbnailer_state.first_visibile;
1756 		file_list->priv->thumbnailer_state.current_item = g_list_nth (list, file_list->priv->thumbnailer_state.current_pos);
1757 		if (file_list->priv->thumbnailer_state.current_item == NULL) {
1758 			file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_COMPLETED;
1759 			return FALSE;
1760 		}
1761 		break;
1762 
1763 	case THUMBNAILER_PHASE_UPDATE_VISIBLE:
1764 		/* Find a non-loaded thumbnail among the visible files. */
1765 
1766 		scan = file_list->priv->thumbnailer_state.current_item;
1767 		pos = file_list->priv->thumbnailer_state.current_pos;
1768 		while (scan && (pos <= file_list->priv->thumbnailer_state.last_visible)) {
1769 			file_data = scan->data;
1770 			thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1771 			if (thumb_data == NULL) {
1772 				file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_COMPLETED;
1773 				return FALSE;
1774 			}
1775 
1776 			if (! thumb_data->thumb_loaded && can_create_file_thumbnail (file_data, thumb_data, current_time, young_file_found)) {
1777 				/* found a thumbnail to load */
1778 				file_list->priv->thumbnailer_state.current_item = scan;
1779 				file_list->priv->thumbnailer_state.current_pos = pos;
1780 				*new_pos = pos;
1781 				return FALSE;
1782 			}
1783 
1784 			pos++;
1785 			scan = scan->next;
1786 		}
1787 
1788 		/* No thumbnail to load among the visible images, pass to the
1789 		 * next phase.  Start from the one after the last visible image. */
1790 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_UPDATE_DOWNWARD;
1791 		file_list->priv->thumbnailer_state.current_pos = file_list->priv->thumbnailer_state.last_visible + 1;
1792 		file_list->priv->thumbnailer_state.current_item = g_list_nth (list, file_list->priv->thumbnailer_state.current_pos);
1793 		break;
1794 
1795 	case THUMBNAILER_PHASE_UPDATE_DOWNWARD:
1796 		scan = file_list->priv->thumbnailer_state.current_item;
1797 		pos = file_list->priv->thumbnailer_state.current_pos;
1798 		while (scan && (pos <= file_list->priv->thumbnailer_state.last_visible + N_CREATEAHEAD)) {
1799 			gboolean requested_action_performed;
1800 
1801 			file_data = scan->data;
1802 			thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1803 			if (thumb_data == NULL) {
1804 				file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_COMPLETED;
1805 				return FALSE;
1806 			}
1807 
1808 			if (pos <= file_list->priv->thumbnailer_state.last_visible + N_VIEWAHEAD)
1809 				requested_action_performed = thumb_data->thumb_loaded;
1810 			else
1811 				requested_action_performed = thumb_data->thumb_created;
1812 
1813 			if (! requested_action_performed && can_create_file_thumbnail (file_data, thumb_data, current_time, young_file_found)) {
1814 				/* found a thumbnail to load */
1815 				file_list->priv->thumbnailer_state.current_item = scan;
1816 				file_list->priv->thumbnailer_state.current_pos = pos;
1817 				*new_pos = pos;
1818 				return FALSE;
1819 			}
1820 
1821 			pos++;
1822 			scan = scan->next;
1823 		}
1824 
1825 		/* No thumbnail to load, pass to the next phase. Start from the
1826 		 * one before the first visible upward to the first one. */
1827 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_UPDATE_UPWARD;
1828 		file_list->priv->thumbnailer_state.current_pos = file_list->priv->thumbnailer_state.first_visibile - 1;
1829 		file_list->priv->thumbnailer_state.current_item = g_list_nth (list, file_list->priv->thumbnailer_state.current_pos);
1830 		break;
1831 
1832 	case THUMBNAILER_PHASE_UPDATE_UPWARD:
1833 		scan = file_list->priv->thumbnailer_state.current_item;
1834 		pos = file_list->priv->thumbnailer_state.current_pos;
1835 		while (scan && (pos >= file_list->priv->thumbnailer_state.first_visibile - N_CREATEAHEAD)) {
1836 			gboolean requested_action_performed;
1837 
1838 			file_data = scan->data;
1839 			thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
1840 			if (thumb_data == NULL) {
1841 				file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_COMPLETED;
1842 				return FALSE;
1843 			}
1844 
1845 			if (pos >= file_list->priv->thumbnailer_state.first_visibile - N_VIEWAHEAD)
1846 				requested_action_performed = thumb_data->thumb_loaded;
1847 			else
1848 				requested_action_performed = thumb_data->thumb_created;
1849 
1850 			if (! requested_action_performed && can_create_file_thumbnail (file_data, thumb_data, current_time, young_file_found)) {
1851 				/* found a thumbnail to load */
1852 				file_list->priv->thumbnailer_state.current_item = scan;
1853 				file_list->priv->thumbnailer_state.current_pos = pos;
1854 				*new_pos = pos;
1855 				return FALSE;
1856 			}
1857 
1858 			pos--;
1859 			scan = scan->prev;
1860 		}
1861 
1862 		/* No thumbnail to load, terminate the process. */
1863 		file_list->priv->thumbnailer_state.phase = THUMBNAILER_PHASE_COMPLETED;
1864 		break;
1865 
1866 	case THUMBNAILER_PHASE_COMPLETED:
1867 		return FALSE;
1868 	}
1869 
1870 	return iterate_again;
1871 }
1872 
1873 
1874 static void
_gth_file_list_update_next_thumb(GthFileList * file_list)1875 _gth_file_list_update_next_thumb (GthFileList *file_list)
1876 {
1877 	int           new_pos;
1878 	GTimeVal      current_time;
1879 	gboolean      young_file_found;
1880 	ThumbnailJob *job;
1881 
1882 	/* give priority to any other operation, the thumbnailer will restart
1883 	 * again soon after the operation terminates. */
1884 	if (file_list->priv->queue != NULL) {
1885 		if (file_list->priv->update_event != 0)
1886 			g_source_remove (file_list->priv->update_event);
1887 		file_list->priv->update_event = g_idle_add (update_thumbs_stopped, file_list);
1888 		return;
1889 	}
1890 
1891 	if (file_list->priv->cancelling)
1892 		return;
1893 
1894 	if (! file_list->priv->load_thumbs) {
1895 		_gth_file_list_thumbs_completed (file_list);
1896 		return;
1897 	}
1898 
1899 	/* find the new thumbnail to load */
1900 
1901 	new_pos = -1;
1902 	g_get_current_time (&current_time);
1903 	young_file_found = FALSE;
1904 	while (_gth_file_list_thumbnailer_iterate (file_list, &new_pos, &current_time, &young_file_found))
1905 		/* void */;
1906 
1907 	if (file_list->priv->thumbnailer_state.phase == THUMBNAILER_PHASE_COMPLETED) {
1908 		_gth_file_list_thumbs_completed (file_list);
1909 		if (young_file_found && (file_list->priv->restart_thumb_update == 0))
1910 			file_list->priv->restart_thumb_update = g_timeout_add (RESTART_LOADING_THUMBS_DELAY, restart_thumb_update_cb, file_list);
1911 		return;
1912 	}
1913 
1914 	g_assert (file_list->priv->thumbnailer_state.current_item != NULL);
1915 
1916 	job = g_new0 (ThumbnailJob, 1);
1917 	job->file_list = g_object_ref (file_list);
1918 	job->loader = g_object_ref (file_list->priv->thumb_loader);
1919 	job->cancellable = g_cancellable_new ();
1920 	job->file_data = g_object_ref (file_list->priv->thumbnailer_state.current_item->data);
1921 	job->pos = file_list->priv->thumbnailer_state.current_pos;
1922 	job->update_in_view = (job->pos >= (file_list->priv->thumbnailer_state.first_visibile - N_VIEWAHEAD)) && (job->pos <= (file_list->priv->thumbnailer_state.last_visible + N_VIEWAHEAD));
1923 
1924 #if 0
1925 	g_print ("%d in [%d, %d] => %d\n",
1926  		 job->pos,
1927 		 (file_list->priv->thumbnailer_state.first_visibile - N_VIEWAHEAD),
1928 		 (file_list->priv->thumbnailer_state.last_visible + N_VIEWAHEAD),
1929 		 job->update_in_view);
1930 #endif
1931 
1932 	_gth_file_list_update_thumb (file_list, job);
1933 }
1934 
1935 
1936 static void
_gth_file_list_exec_next_op(GthFileList * file_list)1937 _gth_file_list_exec_next_op (GthFileList *file_list)
1938 {
1939 	GList         *first;
1940 	GthFileListOp *op;
1941 	gboolean       exec_next_op = TRUE;
1942 
1943 	if (file_list->priv->queue == NULL) {
1944 		start_update_next_thumb (file_list);
1945 		return;
1946 	}
1947 
1948 	first = file_list->priv->queue;
1949 	file_list->priv->queue = g_list_remove_link (file_list->priv->queue, first);
1950 
1951 	op = first->data;
1952 
1953 	switch (op->type) {
1954 	case GTH_FILE_LIST_OP_TYPE_SET_FILES:
1955 		gfl_set_files (file_list, op->file_list);
1956 		break;
1957 	case GTH_FILE_LIST_OP_TYPE_ADD_FILES:
1958 		gfl_add_files (file_list, op->file_list, op->position);
1959 		break;
1960 	case GTH_FILE_LIST_OP_TYPE_DELETE_FILES:
1961 		gfl_delete_files (file_list, op->files);
1962 		break;
1963 	case GTH_FILE_LIST_OP_TYPE_UPDATE_FILES:
1964 		gfl_update_files (file_list, op->file_list);
1965 		break;
1966 	case GTH_FILE_LIST_OP_TYPE_UPDATE_EMBLEMS:
1967 		gfl_update_emblems (file_list, op->file_list);
1968 		break;
1969 	case GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS:
1970 		gfl_enable_thumbs (file_list, op->ival);
1971 		exec_next_op = FALSE;
1972 		break;
1973 	case GTH_FILE_LIST_OP_TYPE_CLEAR_FILES:
1974 		gfl_clear_list (file_list, op->sval);
1975 		break;
1976 	case GTH_FILE_LIST_OP_TYPE_SET_FILTER:
1977 		gfl_set_filter (file_list, op->filter);
1978 		break;
1979 	case GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC:
1980 		gfl_set_sort_func (file_list, op->cmp_func, op->inverse_sort);
1981 		break;
1982 	case GTH_FILE_LIST_OP_TYPE_RENAME_FILE:
1983 		gfl_rename_file (file_list, op->file, op->file_data);
1984 		break;
1985 	case GTH_FILE_LIST_OP_TYPE_MAKE_FILE_VISIBLE:
1986 		gfl_make_file_visible (file_list, op->file);
1987 		break;
1988 	case GTH_FILE_LIST_OP_TYPE_RESTORE_STATE:
1989 		gfl_restore_state (file_list, op->selected, op->vscroll);
1990 		break;
1991 	default:
1992 		exec_next_op = FALSE;
1993 		break;
1994 	}
1995 
1996 	gth_file_list_op_free (op);
1997 	g_list_free (first);
1998 
1999 	if (exec_next_op)
2000 		_gth_file_list_exec_next_op (file_list);
2001 }
2002 
2003 
2004 int
gth_file_list_first_file(GthFileList * file_list,gboolean skip_broken,gboolean only_selected)2005 gth_file_list_first_file (GthFileList *file_list,
2006 			  gboolean     skip_broken,
2007 			  gboolean     only_selected)
2008 {
2009 	GList *files;
2010 	GList *scan;
2011 	int    pos;
2012 
2013 	files = _gth_file_list_get_visibles (file_list);
2014 
2015 	pos = 0;
2016 	for (scan = files; scan; scan = scan->next, pos++) {
2017 		GthFileData *file_data = scan->data;
2018 		ThumbData   *thumb_data;
2019 
2020 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
2021 		if (skip_broken && thumb_data->error)
2022 			continue;
2023 		if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_list->priv->view), pos))
2024 			continue;
2025 
2026 		return pos;
2027 	}
2028 
2029 	return -1;
2030 }
2031 
2032 
2033 int
gth_file_list_last_file(GthFileList * file_list,gboolean skip_broken,gboolean only_selected)2034 gth_file_list_last_file (GthFileList *file_list,
2035 			 gboolean     skip_broken,
2036 			 gboolean     only_selected)
2037 {
2038 	GList *files;
2039 	GList *scan;
2040 	int    pos;
2041 
2042 	files = _gth_file_list_get_visibles (file_list);
2043 
2044 	pos = g_list_length (files) - 1;
2045 	if (pos < 0)
2046 		return -1;
2047 
2048 	for (scan = g_list_nth (files, pos); scan; scan = scan->prev, pos--) {
2049 		GthFileData *file_data = scan->data;
2050 		ThumbData   *thumb_data;
2051 
2052 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
2053 		if (skip_broken && thumb_data->error)
2054 			continue;
2055 		if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_list->priv->view), pos))
2056 			continue;
2057 
2058 		return pos;
2059 	}
2060 
2061 	return -1;
2062 }
2063 
2064 
2065 int
gth_file_list_next_file(GthFileList * file_list,int pos,gboolean skip_broken,gboolean only_selected,gboolean wrap)2066 gth_file_list_next_file (GthFileList *file_list,
2067 			 int          pos,
2068 			 gboolean     skip_broken,
2069 			 gboolean     only_selected,
2070 			 gboolean     wrap)
2071 {
2072 	GList *files;
2073 	GList *scan;
2074 
2075 	files = _gth_file_list_get_visibles (file_list);
2076 
2077 	pos++;
2078 	if (pos >= 0)
2079 		scan = g_list_nth (files, pos);
2080 	else if (wrap)
2081 		scan = g_list_first (files);
2082 	else
2083 		scan = NULL;
2084 
2085 	for (/* void */; scan; scan = scan->next, pos++) {
2086 		GthFileData *file_data = scan->data;
2087 		ThumbData   *thumb_data;
2088 
2089 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
2090 		if (skip_broken && thumb_data->error)
2091 			continue;
2092 		if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_list->priv->view), pos))
2093 			continue;
2094 
2095 		break;
2096 	}
2097 
2098 	return (scan != NULL) ? pos : -1;
2099 }
2100 
2101 
2102 int
gth_file_list_prev_file(GthFileList * file_list,int pos,gboolean skip_broken,gboolean only_selected,gboolean wrap)2103 gth_file_list_prev_file (GthFileList *file_list,
2104 			 int          pos,
2105 			 gboolean     skip_broken,
2106 			 gboolean     only_selected,
2107 			 gboolean     wrap)
2108 {
2109 	GList *files;
2110 	GList *scan;
2111 
2112 	files = _gth_file_list_get_visibles (file_list);
2113 
2114 	pos--;
2115 	if (pos >= 0)
2116 		scan = g_list_nth (files, pos);
2117 	else if (wrap) {
2118 		pos = g_list_length (files) - 1;
2119 		scan = g_list_nth (files, pos);
2120 	}
2121 	else
2122 		scan = NULL;
2123 
2124 	for (/* void */; scan; scan = scan->prev, pos--) {
2125 		GthFileData *file_data = scan->data;
2126 		ThumbData   *thumb_data;
2127 
2128 		thumb_data = g_hash_table_lookup (file_list->priv->thumb_data, file_data->file);
2129 		if (skip_broken && thumb_data->error)
2130 			continue;
2131 		if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (file_list->priv->view), pos))
2132 			continue;
2133 
2134 		break;
2135 	}
2136 
2137 	return (scan != NULL) ? pos : -1;
2138 }
2139 
2140 
2141 void
gth_file_list_enable_drag_source(GthFileList * file_list,GdkDragAction actions)2142 gth_file_list_enable_drag_source (GthFileList     *file_list,
2143 				  GdkDragAction    actions)
2144 {
2145 	GtkTargetList  *target_list;
2146 	GtkTargetEntry *targets;
2147 	int             n_targets;
2148 
2149 	target_list = gtk_target_list_new (NULL, 0);
2150 	gtk_target_list_add_uri_targets (target_list, 0);
2151 	gtk_target_list_add_text_targets (target_list, 0);
2152 	targets = gtk_target_table_new_from_list (target_list, &n_targets);
2153 	gth_file_view_enable_drag_source (GTH_FILE_VIEW (file_list->priv->view),
2154 					  GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
2155 					  targets,
2156 					  n_targets,
2157 					  actions);
2158 
2159 	gtk_target_list_unref (target_list);
2160 	gtk_target_table_free (targets, n_targets);
2161 }
2162 
2163 
2164 void
gth_file_list_unset_drag_source(GthFileList * file_list)2165 gth_file_list_unset_drag_source (GthFileList *file_list)
2166 {
2167 	gth_file_view_unset_drag_source (GTH_FILE_VIEW (file_list->priv->view));
2168 }
2169 
2170 
2171 void
gth_file_list_focus(GthFileList * file_list)2172 gth_file_list_focus (GthFileList *file_list)
2173 {
2174 	GtkWidget *child;
2175 
2176 	child = gtk_stack_get_visible_child (GTK_STACK (file_list->priv->notebook));
2177 	if (GTK_IS_BIN (child))
2178 		child = gtk_bin_get_child (GTK_BIN (child));
2179 	gtk_widget_grab_focus ((child != NULL) ? child : GTK_WIDGET (file_list));
2180 }
2181