1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 The 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 <gtk/gtk.h>
24 #include <gthumb.h>
25 #include "dlg-edit-metadata.h"
26 #include "gth-edit-metadata-dialog.h"
27 
28 
29 #define UPDATE_SELECTION_DELAY 50
30 
31 
32 typedef struct {
33 	int         ref;
34 	GthBrowser *browser;
35 	GtkWidget  *dialog;
36 	GtkWidget  *keep_open_checkbutton;
37 	GtkWidget  *info;
38 	char       *dialog_name;
39 	GList      *file_list; /* GthFileData list */
40 	GList      *parents;
41 	gboolean    never_shown;
42 	gboolean    close_dialog;
43 	GthTask    *loader;
44 	gulong      file_selection_changed_event;
45 	guint       update_selectection_event;
46 } DialogData;
47 
48 
49 static DialogData *
dialog_data_ref(DialogData * data)50 dialog_data_ref (DialogData *data)
51 {
52 	g_atomic_int_inc (&data->ref);
53 	return data;
54 }
55 
56 
57 static void
cancel_file_list_loading(DialogData * data)58 cancel_file_list_loading (DialogData *data)
59 {
60 	if (data->loader == NULL)
61 		return;
62 	gth_task_cancel (data->loader);
63 	g_object_unref (data->loader);
64 	data->loader = NULL;
65 }
66 
67 
68 static void
dialog_data_unref(DialogData * data)69 dialog_data_unref (DialogData *data)
70 {
71 	if (! g_atomic_int_dec_and_test (&data->ref))
72 		return;
73 
74 	if (data->file_selection_changed_event != 0) {
75 		g_signal_handler_disconnect (gth_browser_get_file_list_view (data->browser),
76 					     data->file_selection_changed_event);
77 		data->file_selection_changed_event = 0;
78 	}
79 	if (data->update_selectection_event != 0) {
80 		g_source_remove (data->update_selectection_event);
81 		data->update_selectection_event = 0;
82 	}
83 	cancel_file_list_loading (data);
84 
85 	gth_browser_set_dialog (data->browser, data->dialog_name, NULL);
86 	gtk_widget_destroy (data->dialog);
87 
88 	g_free (data->dialog_name);
89 	_g_object_list_unref (data->file_list);
90 	_g_object_list_unref (data->parents);
91 	g_free (data);
92 }
93 
94 
95 static void
close_dialog(DialogData * data)96 close_dialog (DialogData *data)
97 {
98 	if (data->file_selection_changed_event != 0) {
99 		g_signal_handler_disconnect (gth_browser_get_file_list_view (data->browser),
100 					     data->file_selection_changed_event);
101 		data->file_selection_changed_event = 0;
102 	}
103 	gtk_widget_hide (data->dialog);
104 	dialog_data_unref (data);
105 }
106 
107 
108 static void
saver_completed_cb(GthTask * task,GError * error,gpointer user_data)109 saver_completed_cb (GthTask  *task,
110 		    GError   *error,
111 		    gpointer  user_data)
112 {
113 	DialogData *data = user_data;
114 	GthMonitor *monitor;
115 	GList      *scan;
116 
117 	monitor = gth_main_get_default_monitor ();
118 	for (scan = data->parents; scan; scan = scan->next)
119 		gth_monitor_resume (monitor, (GFile *) scan->data);
120 
121 	if (error != NULL) {
122 		if (! g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
123 			_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not save the file metadata"), error);
124 	}
125 	else {
126 		for (scan = data->file_list; scan; scan = scan->next) {
127 			GthFileData *file_data = scan->data;
128 			GFile       *parent;
129 			GList       *files;
130 
131 			parent = g_file_get_parent (file_data->file);
132 			files = g_list_prepend (NULL, g_object_ref (file_data->file));
133 			gth_monitor_folder_changed (monitor, parent, files, GTH_MONITOR_EVENT_CHANGED);
134 			gth_monitor_metadata_changed (monitor, file_data);
135 
136 			_g_object_list_unref (files);
137 		}
138 	}
139 
140 	if (data->close_dialog)
141 		close_dialog (data);
142 	else
143 		gth_browser_show_next_image (data->browser, FALSE, FALSE);
144 
145 	dialog_data_unref (data);
146 	_g_object_unref (task);
147 }
148 
149 
150 static void
edit_metadata_dialog__response_cb(GtkDialog * dialog,int response,gpointer user_data)151 edit_metadata_dialog__response_cb (GtkDialog *dialog,
152 				   int        response,
153 				   gpointer   user_data)
154 {
155 	DialogData *data = user_data;
156 	GthMonitor *monitor;
157 	GHashTable *parents;
158 	GList      *scan;
159 	GthTask    *task;
160 
161 	if (response != GTK_RESPONSE_OK) {
162 		cancel_file_list_loading (data);
163 		close_dialog (data);
164 		return;
165 	}
166 
167 	if (data->file_list == NULL)
168 		return;
169 
170 	data->close_dialog = ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (data->keep_open_checkbutton));
171 
172 	/* get the parents list */
173 
174 	parents = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL);
175 	for (scan = data->file_list; scan; scan = scan->next) {
176 		GthFileData *file_data = scan->data;
177 		GFile       *parent;
178 
179 		parent = g_file_get_parent (file_data->file);
180 		if (G_LIKELY (parent != NULL)) {
181 			if (g_hash_table_lookup (parents, parent) == NULL)
182 				g_hash_table_insert (parents, g_object_ref (parent), GINT_TO_POINTER (1));
183 			g_object_unref (parent);
184 		}
185 	}
186 	_g_object_list_unref (data->parents);
187 	data->parents = g_hash_table_get_keys (parents);
188 	g_list_foreach (data->parents, (GFunc) g_object_ref, NULL);
189 	g_hash_table_unref (parents);
190 
191 	/* ignore changes to all the parents */
192 
193 	monitor = gth_main_get_default_monitor ();
194 	for (scan = data->parents; scan; scan = scan->next)
195 		gth_monitor_pause (monitor, (GFile *) scan->data);
196 
197 	gth_edit_metadata_dialog_update_info (GTH_EDIT_METADATA_DIALOG (data->dialog), data->file_list);
198 
199 	dialog_data_ref (data);
200 	task = gth_save_file_data_task_new (data->file_list, "*");
201 	g_signal_connect (task,
202 			  "completed",
203 			  G_CALLBACK (saver_completed_cb),
204 			  data);
205 	gth_browser_exec_task (data->browser, task, GTH_TASK_FLAGS_IGNORE_ERROR);
206 }
207 
208 
209 typedef struct {
210 	DialogData *data;
211 	GList      *files;
212 } LoaderData;
213 
214 
215 static void
loader_data_free(LoaderData * loader_data)216 loader_data_free (LoaderData *loader_data)
217 {
218 	dialog_data_unref (loader_data->data);
219 	_g_object_list_unref (loader_data->files);
220 	g_free (loader_data);
221 }
222 
223 
224 static void
loader_completed_cb(GthTask * task,GError * error,gpointer user_data)225 loader_completed_cb (GthTask  *task,
226 		     GError   *error,
227 		     gpointer  user_data)
228 {
229 	LoaderData *loader_data = user_data;
230 	DialogData *data = loader_data->data;
231 
232 	if (error != NULL) {
233 		if (! g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
234 			_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Cannot read file information"), error);
235 		loader_data_free (loader_data);
236 		if (data->never_shown)
237 			close_dialog (data);
238 		return;
239 	}
240 
241 	_g_object_list_unref (data->file_list);
242 	data->file_list = _g_object_list_ref (gth_load_file_data_task_get_result (GTH_LOAD_FILE_DATA_TASK (task)));
243 
244 	gth_file_selection_info_set_file_list (GTH_FILE_SELECTION_INFO (data->info), data->file_list);
245 	gth_edit_metadata_dialog_set_file_list (GTH_EDIT_METADATA_DIALOG (data->dialog), data->file_list);
246 
247 	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (data->browser));
248 	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
249 	gtk_window_present (GTK_WINDOW (data->dialog));
250 
251 	data->never_shown = FALSE;
252 
253 	loader_data_free (loader_data);
254 }
255 
256 
257 static gboolean
update_file_list(gpointer user_data)258 update_file_list (gpointer user_data)
259 {
260 	DialogData *data = user_data;
261 	LoaderData *loader_data;
262 	GList      *items;
263 	GList      *file_data_list;
264 
265 	if (data->update_selectection_event != 0) {
266 		g_source_remove (data->update_selectection_event);
267 		data->update_selectection_event = 0;
268 	}
269 
270 	cancel_file_list_loading (data);
271 
272 	loader_data = g_new0 (LoaderData, 1);
273 	loader_data->data = dialog_data_ref (data);
274 
275 	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (data->browser)));
276 	file_data_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (data->browser)), items);
277 	loader_data->files = gth_file_data_list_to_file_list (file_data_list);
278 
279 	gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, loader_data->files != NULL);
280 
281 	data->loader = gth_load_file_data_task_new (loader_data->files, "*");
282 	g_signal_connect (data->loader,
283 			  "completed",
284 			  G_CALLBACK (loader_completed_cb),
285 			  loader_data);
286 	gth_browser_exec_task (data->browser, data->loader, GTH_TASK_FLAGS_IGNORE_ERROR);
287 
288 	_g_object_list_unref (file_data_list);
289 	_gtk_tree_path_list_free (items);
290 
291 	return FALSE;
292 }
293 
294 
295 static void
file_selection_changed_cb(GthFileSelection * self,DialogData * data)296 file_selection_changed_cb (GthFileSelection *self,
297 			   DialogData       *data)
298 {
299 	if (data->update_selectection_event != 0)
300 		g_source_remove (data->update_selectection_event);
301 	data->update_selectection_event = g_timeout_add (UPDATE_SELECTION_DELAY, update_file_list, data);
302 }
303 
304 
305 static void
keep_open_button_toggled_cb(GtkToggleButton * button,DialogData * data)306 keep_open_button_toggled_cb (GtkToggleButton *button,
307 			     DialogData      *data)
308 {
309 	gth_file_selection_info_set_visible (GTH_FILE_SELECTION_INFO (data->info),
310 					     gtk_toggle_button_get_active (button));
311 }
312 
313 
314 void
dlg_edit_metadata(GthBrowser * browser,GType dialog_type,const char * dialog_name)315 dlg_edit_metadata (GthBrowser *browser,
316 		   GType       dialog_type,
317 		   const char *dialog_name)
318 {
319 	DialogData *data;
320 
321 	if (gth_browser_get_dialog (browser, dialog_name)) {
322 		gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, dialog_name)));
323 		return;
324 	}
325 
326 	data = g_new0 (DialogData, 1);
327 	data->ref = 1;
328 	data->browser = browser;
329 	data->dialog = g_object_new (dialog_type,
330 				     "transient-for", GTK_WINDOW (browser),
331 				     "modal", FALSE,
332 				     "use-header-bar", _gtk_settings_get_dialogs_use_header (),
333 				     NULL);
334 	data->dialog_name = g_strdup (dialog_name);
335 	data->never_shown = TRUE;
336 
337 	data->info = gth_file_selection_info_new ();
338 	gtk_widget_show (data->info);
339 	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (data->dialog))), data->info, FALSE, FALSE, 0);
340 
341 	gtk_dialog_add_buttons (GTK_DIALOG (data->dialog),
342 				_GTK_LABEL_CLOSE, GTK_RESPONSE_CANCEL,
343 				_GTK_LABEL_SAVE, GTK_RESPONSE_OK,
344 				NULL);
345 
346 	data->keep_open_checkbutton = _gtk_toggle_image_button_new_for_header_bar ("pinned-symbolic");
347 	gtk_widget_set_tooltip_text (data->keep_open_checkbutton, _("Keep the dialog open"));
348 	gtk_widget_show (data->keep_open_checkbutton);
349 	_gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog), data->keep_open_checkbutton);
350 
351 	_gtk_dialog_add_class_to_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, GTK_STYLE_CLASS_SUGGESTED_ACTION);
352 	gth_browser_set_dialog (browser, data->dialog_name, data->dialog);
353 
354 	g_signal_connect (G_OBJECT (data->dialog),
355 			  "delete-event",
356 			  G_CALLBACK (gtk_true),
357 			  NULL);
358 	g_signal_connect (data->dialog,
359 			  "response",
360 			  G_CALLBACK (edit_metadata_dialog__response_cb),
361 			  data);
362 	g_signal_connect (data->keep_open_checkbutton,
363 			  "toggled",
364 			  G_CALLBACK (keep_open_button_toggled_cb),
365 			  data);
366 	data->file_selection_changed_event =
367 			g_signal_connect (gth_browser_get_file_list_view (data->browser),
368 					  "file-selection-changed",
369 					  G_CALLBACK (file_selection_changed_cb),
370 					  data);
371 
372 	update_file_list (data);
373 }
374