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