1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include "config.h"
21 #include "gettext.h"
22 #include <libxml/encoding.h>
23 #include <libxml/xmlwriter.h>
24 #include "rs-camera-db.h"
25 #include "rs-photo.h"
26 #include "rs-toolbox.h"
27 #include "rs-cache.h"
28 
29 /* FIXME: Make this thread safe! */
30 
31 struct _RSCameraDb {
32 	GObject parent;
33 
34 	gchar *path;
35 	GtkListStore *cameras;
36 };
37 
38 enum {
39 	COLUMN_MAKE,
40 	COLUMN_MODEL,
41 	COLUMN_PROFILE,
42 	COLUMN_SETTINGS0,
43 	COLUMN_SETTINGS1,
44 	COLUMN_SETTINGS2,
45 	NUM_COLUMNS
46 };
47 
48 G_DEFINE_TYPE(RSCameraDb, rs_camera_db, G_TYPE_OBJECT)
49 
50 static void load_db(RSCameraDb *db);
51 static void save_db(RSCameraDb *db);
52 
53 enum {
54 	PROP_0,
55 	PROP_PATH
56 };
57 
58 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)59 get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
60 {
61 	RSCameraDb *camera_db = RS_CAMERA_DB(object);
62 
63 	switch (property_id)
64 	{
65 		case PROP_PATH:
66 			g_value_set_string(value, camera_db->path);
67 			break;
68 		default:
69 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
70 	}
71 }
72 
73 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)74 set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
75 {
76 	RSCameraDb *camera_db = RS_CAMERA_DB(object);
77 
78 	switch (property_id)
79 	{
80 		case PROP_PATH:
81 			camera_db->path = g_value_dup_string(value);
82 			load_db(camera_db);
83 			break;
84 		default:
85 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
86 	}
87 }
88 
89 static void
dispose(GObject * object)90 dispose(GObject *object)
91 {
92 	G_OBJECT_CLASS(rs_camera_db_parent_class)->dispose(object);
93 }
94 
95 static void
rs_camera_db_class_init(RSCameraDbClass * klass)96 rs_camera_db_class_init(RSCameraDbClass *klass)
97 {
98 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
99 
100 	object_class->get_property = get_property;
101 	object_class->set_property = set_property;
102 	object_class->dispose = dispose;
103 
104 	g_object_class_install_property(object_class,
105 		PROP_PATH, g_param_spec_string(
106 		"path", "Path", "Path to XML database",
107 		NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
108 }
109 
110 static void
rs_camera_db_init(RSCameraDb * camera_db)111 rs_camera_db_init(RSCameraDb *camera_db)
112 {
113 	camera_db->cameras = gtk_list_store_new(NUM_COLUMNS,
114 		G_TYPE_STRING, /* COLUMN_MAKE */
115 		G_TYPE_STRING, /* COLUMN_MODEL */
116 		G_TYPE_POINTER, /* COLUMN_PROFILE */
117 		RS_TYPE_SETTINGS, /* COLUMN_SETTINGS0 */
118 		RS_TYPE_SETTINGS, /* COLUMN_SETTINGS1 */
119 		RS_TYPE_SETTINGS /* COLUMN_SETTINGS2 */
120 	);
121 }
122 
123 RSCameraDb *
rs_camera_db_new(const char * path)124 rs_camera_db_new(const char *path)
125 {
126 	g_assert(path != NULL);
127 	g_assert(g_path_is_absolute(path));
128 
129 	return g_object_new (RS_TYPE_CAMERA_DB, "path", path, NULL);
130 }
131 
132 RSCameraDb *
rs_camera_db_get_singleton(void)133 rs_camera_db_get_singleton(void)
134 {
135 	static RSCameraDb *camera_db = NULL;
136 	static GStaticMutex lock = G_STATIC_MUTEX_INIT;
137 
138 	g_static_mutex_lock(&lock);
139 	if (!camera_db)
140 	{
141 		gchar *path = g_build_filename(rs_confdir_get(), "camera-database.xml", NULL);
142 		camera_db = rs_camera_db_new(path);
143 		g_free(path);
144 	}
145 	g_static_mutex_unlock(&lock);
146 
147 	return camera_db;
148 }
149 
150 static void
camera_db_add_camera(RSCameraDb * camera_db,const gchar * make,const gchar * model)151 camera_db_add_camera(RSCameraDb *camera_db, const gchar *make, const gchar *model)
152 {
153 	GtkTreeIter iter;
154 
155 	gtk_list_store_append(camera_db->cameras, &iter);
156 
157 	gtk_list_store_set(camera_db->cameras, &iter,
158 		COLUMN_MAKE, make,
159 		COLUMN_MODEL, model,
160 		-1);
161 
162 	save_db(camera_db);
163 }
164 
165 void
rs_camera_db_save_defaults(RSCameraDb * camera_db,RS_PHOTO * photo)166 rs_camera_db_save_defaults(RSCameraDb *camera_db, RS_PHOTO *photo)
167 {
168 	g_return_if_fail(RS_IS_PHOTO(photo));
169 	g_return_if_fail(RS_IS_METADATA(photo->metadata));
170 
171 	gboolean found = FALSE;
172 	gint snapshot;
173 	gchar *db_make, *db_model;
174 	const gchar *needle_make = photo->metadata->make_ascii;
175 	const gchar *needle_model = photo->metadata->model_ascii;
176 
177 	GtkTreeIter iter;
178 	GtkTreeModel *model = GTK_TREE_MODEL(camera_db->cameras);
179 
180 	if (needle_make && needle_model && gtk_tree_model_get_iter_first(model, &iter))
181 		do {
182 			gtk_tree_model_get(model, &iter,
183 				COLUMN_MAKE, &db_make,
184 				COLUMN_MODEL, &db_model,
185 				-1);
186 			if (db_make && db_model && g_str_equal(needle_make, db_make) && g_str_equal(needle_model, db_model))
187 			{
188 				gpointer profile = rs_photo_get_dcp_profile(photo);
189 
190 				gtk_list_store_set(camera_db->cameras, &iter,
191 					COLUMN_PROFILE, profile,
192 					-1);
193 
194 				RSSettings *settings[3];
195 
196 				for(snapshot=0;snapshot<3;snapshot++)
197 				{
198 					settings[snapshot] = rs_settings_new();
199 					rs_settings_copy(photo->settings[snapshot], MASK_ALL, settings[snapshot]);
200 					gtk_list_store_set(camera_db->cameras, &iter,
201 						COLUMN_SETTINGS0 + snapshot, settings[snapshot],
202 						-1);
203 					g_object_unref(settings[snapshot]);
204 				}
205 
206 				found = TRUE;
207 			}
208 			g_free(db_make);
209 			g_free(db_model);
210 		} while (!found && gtk_tree_model_iter_next(model, &iter));
211 
212 	save_db(camera_db);
213 }
214 
215 gboolean
rs_camera_db_photo_get_defaults(RSCameraDb * camera_db,RS_PHOTO * photo,RSSettings ** dest_settings,gpointer * dest_profile)216 rs_camera_db_photo_get_defaults(RSCameraDb *camera_db, RS_PHOTO *photo, RSSettings **dest_settings, gpointer *dest_profile)
217 {
218 	g_return_val_if_fail(RS_IS_PHOTO(photo), FALSE);
219 	g_return_val_if_fail(RS_IS_METADATA(photo->metadata), FALSE);
220 
221 	gboolean found = FALSE;
222 
223 	gchar *db_make, *db_model;
224 	const gchar *needle_make = photo->metadata->make_ascii;
225 	const gchar *needle_model = photo->metadata->model_ascii;
226 
227 	GtkTreeIter iter;
228 	GtkTreeModel *model = GTK_TREE_MODEL(camera_db->cameras);
229 
230 	if (needle_make && needle_model && gtk_tree_model_get_iter_first(model, &iter))
231 		do {
232 			gtk_tree_model_get(model, &iter,
233 				COLUMN_MAKE, &db_make,
234 				COLUMN_MODEL, &db_model,
235 				-1);
236 			if (db_make && db_model && g_str_equal(needle_make, db_make) && g_str_equal(needle_model, db_model))
237 			{
238 
239 				gtk_tree_model_get(model, &iter,
240 					COLUMN_PROFILE, dest_profile,
241 					COLUMN_SETTINGS0, &dest_settings[0],
242 					COLUMN_SETTINGS1, &dest_settings[1],
243 					COLUMN_SETTINGS2, &dest_settings[2],
244 					-1);
245 
246 				found = TRUE;
247 			}
248 			g_free(db_make);
249 			g_free(db_model);
250 		} while (!found && gtk_tree_model_iter_next(model, &iter));
251 
252 	if (!found)
253 		camera_db_add_camera(camera_db, needle_make, needle_model);
254 
255 	return found;
256 }
257 
258 gboolean
rs_camera_db_photo_set_defaults(RSCameraDb * camera_db,RS_PHOTO * photo)259 rs_camera_db_photo_set_defaults(RSCameraDb *camera_db, RS_PHOTO *photo)
260 {
261 	g_return_val_if_fail(RS_IS_PHOTO(photo), FALSE);
262 	g_return_val_if_fail(RS_IS_METADATA(photo->metadata), FALSE);
263 
264 	gpointer p;
265 	RSSettings *s[3];
266 	gboolean found = rs_camera_db_photo_get_defaults(camera_db, photo, s, &p);
267 
268 	if (!found)
269 		return FALSE;
270 
271 	if (RS_IS_DCP_FILE(p))
272 		rs_photo_set_dcp_profile(photo, p);
273 
274 	gint i;
275 	for(i=0;i<3;i++)
276 		if (RS_IS_SETTINGS(s[i]))
277 		{
278 			rs_settings_copy(s[i], MASK_ALL, photo->settings[i]);
279 			g_object_unref(s[i]);
280 		}
281 
282 	return found;
283 }
284 
285 static void
load_db(RSCameraDb * camera_db)286 load_db(RSCameraDb *camera_db)
287 {
288 	xmlDocPtr doc;
289 	xmlNodePtr cur;
290 	xmlNodePtr entry = NULL;
291 	xmlChar *val;
292 	RSProfileFactory *profile_factory = rs_profile_factory_new_default();
293 
294 	doc = xmlParseFile(camera_db->path);
295 	if (!doc)
296 		return;
297 
298 	cur = xmlDocGetRootElement(doc);
299 	if (cur && (xmlStrcmp(cur->name, BAD_CAST "rawstudio-camera-database") == 0))
300 	{
301 		cur = cur->xmlChildrenNode;
302 		while(cur)
303 		{
304 			if ((!xmlStrcmp(cur->name, BAD_CAST "camera")))
305 			{
306 				GtkTreeIter iter;
307 
308 				gtk_list_store_append(camera_db->cameras, &iter);
309 
310 				entry = cur->xmlChildrenNode;
311 
312 				while (entry)
313 				{
314 					val = xmlNodeListGetString(doc, entry->xmlChildrenNode, 1);
315 					if ((!xmlStrcmp(entry->name, BAD_CAST "make")))
316 						gtk_list_store_set(camera_db->cameras, &iter, COLUMN_MAKE, val, -1);
317 					else if ((!xmlStrcmp(entry->name, BAD_CAST "model")))
318 						gtk_list_store_set(camera_db->cameras, &iter, COLUMN_MODEL, val, -1);
319 					else if ((!xmlStrcmp(entry->name, BAD_CAST "dcp-profile")))
320 						gtk_list_store_set(camera_db->cameras, &iter, COLUMN_PROFILE, rs_profile_factory_find_from_id(profile_factory, (gchar *) val), -1);
321 					xmlFree(val);
322 
323 					if ((!xmlStrcmp(entry->name, BAD_CAST "settings")))
324 					{
325 						val = xmlGetProp(entry, BAD_CAST "id");
326 						gint id = (val) ? atoi((gchar *) val) : 0;
327 						xmlFree(val);
328 						id = CLAMP(id, 0, 2);
329 						RSSettings *settings = rs_settings_new();
330 						rs_cache_load_setting(settings, doc, entry->xmlChildrenNode, 100); /* FIXME: Correct version somehow! */
331 						gtk_list_store_set(camera_db->cameras, &iter, COLUMN_SETTINGS0 + id, settings, -1);
332 						g_object_unref(settings);
333 					}
334 					entry = entry->next;
335 				}
336 			}
337 			cur = cur->next;
338 		}
339 	}
340 	else
341 		g_warning(PACKAGE " did not understand the format in %s", camera_db->path);
342 
343 	xmlFreeDoc(doc);
344 }
345 
346 static void
save_db(RSCameraDb * camera_db)347 save_db(RSCameraDb *camera_db)
348 {
349 	xmlTextWriterPtr writer;
350 	gint snapshot;
351 	gchar *db_make, *db_model;
352 	gpointer profile;
353 	RSSettings *settings[3];
354 	GtkTreeIter iter;
355 	GtkTreeModel *model = GTK_TREE_MODEL(camera_db->cameras);
356 
357 	writer = xmlNewTextWriterFilename(camera_db->path, 0);
358 	if (!writer)
359 		return;
360 
361 	xmlTextWriterSetIndent(writer, 1);
362 	xmlTextWriterStartDocument(writer, NULL, "ISO-8859-1", NULL);
363 	xmlTextWriterStartElement(writer, BAD_CAST "rawstudio-camera-database");
364 
365 	if (gtk_tree_model_get_iter_first(model, &iter))
366 		do {
367 			gtk_tree_model_get(model, &iter,
368 				COLUMN_MAKE, &db_make,
369 				COLUMN_MODEL, &db_model,
370 				COLUMN_PROFILE, &profile,
371 				COLUMN_SETTINGS0, &settings[0],
372 				COLUMN_SETTINGS1, &settings[1],
373 				COLUMN_SETTINGS2, &settings[2],
374 				-1);
375 
376 			xmlTextWriterStartElement(writer, BAD_CAST "camera");
377 
378 			if (db_make)
379 				xmlTextWriterWriteFormatElement(writer, BAD_CAST "make", "%s", db_make);
380 			if (db_model)
381 				xmlTextWriterWriteFormatElement(writer, BAD_CAST "model", "%s", db_model);
382 
383 			if (profile)
384 			{
385 				if (RS_IS_DCP_FILE(profile))
386 				{
387 					const gchar* dcp_id = rs_dcp_get_id(RS_DCP_FILE(profile));
388 					xmlTextWriterWriteFormatElement(writer, BAD_CAST "dcp-profile", "%s", dcp_id);
389 				}
390 				/* FIXME: Add support for ICC profiles */
391 			}
392 
393 			for(snapshot=0;snapshot<3;snapshot++)
394 				if (RS_IS_SETTINGS(settings[snapshot]))
395 				    {
396 						xmlTextWriterStartElement(writer, BAD_CAST "settings");
397 						xmlTextWriterWriteFormatAttribute(writer, BAD_CAST "id", "%d", snapshot);
398 						rs_cache_save_settings(settings[snapshot], MASK_ALL-MASK_WB, writer);
399 						xmlTextWriterEndElement(writer);
400 						g_object_unref(settings[snapshot]);
401 					}
402 
403 			xmlTextWriterEndElement(writer);
404 
405 			g_free(db_make);
406 			g_free(db_model);
407 		} while (gtk_tree_model_iter_next(model, &iter));
408 
409 	xmlTextWriterEndDocument(writer);
410 	xmlFreeTextWriter(writer);
411 
412 	return;
413 }
414 
415 static void
icon_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)416 icon_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
417 {
418 	/* This will always be called from the GTK+ main thread, we should be safe */
419 	static GdkPixbuf *pixbuf = NULL;
420 
421 	if (!pixbuf)
422 		pixbuf = gdk_pixbuf_new_from_file(PACKAGE_DATA_DIR G_DIR_SEPARATOR_S "pixmaps" G_DIR_SEPARATOR_S PACKAGE G_DIR_SEPARATOR_S "camera-photo.png", NULL);
423 
424 	g_object_set(cell, "pixbuf", pixbuf, NULL);
425 }
426 
427 GtkWidget *
rs_camera_db_editor_new(RSCameraDb * camera_db)428 rs_camera_db_editor_new(RSCameraDb *camera_db)
429 {
430 	GtkWidget *dialog = NULL;
431 
432 	if (dialog)
433 		return dialog;
434 
435 	dialog = gtk_dialog_new();
436 
437 	gtk_window_set_title(GTK_WINDOW(dialog), _("Camera defaults editor"));
438 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
439 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
440 
441 	gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
442 
443 	/* Is this wise? */
444 	g_signal_connect_swapped(dialog, "delete_event", G_CALLBACK (gtk_widget_hide), dialog);
445 	g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_hide), dialog);
446 
447 #if GTK_CHECK_VERSION(2,14,0)
448 	GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
449 #else
450 	GtkWidget *vbox = GTK_DIALOG(dialog)->vbox;
451 #endif
452 
453 	GtkWidget *camera_selector = gtk_tree_view_new_with_model(GTK_TREE_MODEL(camera_db->cameras));
454 
455     GtkCellRenderer *renderer;
456     GtkTreeViewColumn *column;
457 
458 	column = gtk_tree_view_column_new();
459 	gtk_tree_view_column_set_title (column, _("Model"));
460 
461 	/* Icon */
462 	renderer = gtk_cell_renderer_pixbuf_new();
463 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
464 	gtk_tree_view_column_set_cell_data_func(column, renderer, icon_func, NULL, NULL);
465 
466 	/* Model */
467 	renderer = gtk_cell_renderer_text_new();
468 	gtk_tree_view_column_pack_start(column, renderer, TRUE);
469 	gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_MODEL, NULL);
470 
471 	gtk_tree_view_append_column(GTK_TREE_VIEW(camera_selector), column);
472 
473 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(camera_db->cameras), COLUMN_MODEL, GTK_SORT_ASCENDING);
474 
475 	GtkWidget *hbox = gtk_hbox_new(FALSE, 4);
476 
477 	gtk_box_pack_start(GTK_BOX(hbox), camera_selector, FALSE, FALSE, 3);
478 
479 	GtkWidget *toolbox = rs_toolbox_new();
480 	gtk_box_pack_start(GTK_BOX(hbox), toolbox, TRUE, TRUE, 3);
481 
482 	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 3);
483 
484 	return dialog;
485 }
486