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 "rs-settings.h"
21 #include "rs-utils.h"
22 #include <config.h>
23 #include "gettext.h"
24 #include <string.h> /* memcmp() */
25 
26 G_DEFINE_TYPE (RSSettings, rs_settings, G_TYPE_OBJECT)
27 
28 enum {
29 	SETTINGS_CHANGED,
30 	WB_CHANGED,
31 	LAST_SIGNAL
32 };
33 
34 static guint signals[LAST_SIGNAL] = { 0 };
35 
36 static void get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
37 static void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
38 
39 static void
rs_settings_finalize(GObject * object)40 rs_settings_finalize (GObject *object)
41 {
42 	if (G_OBJECT_CLASS (rs_settings_parent_class)->finalize)
43 		G_OBJECT_CLASS (rs_settings_parent_class)->finalize (object);
44 }
45 
46 enum {
47 	PROP_0,
48 	PROP_EXPOSURE,
49 	PROP_SATURATION,
50 	PROP_HUE,
51 	PROP_CONTRAST,
52 	PROP_WARMTH,
53 	PROP_TINT,
54 	PROP_DCP_TEMP,
55 	PROP_DCP_TINT,
56 	PROP_WB_ASCII,
57 	PROP_SHARPEN,
58 	PROP_DENOISE_LUMA,
59 	PROP_DENOISE_CHROMA,
60 	PROP_TCA_KR,
61 	PROP_TCA_KB,
62 	PROP_VIGNETTING,
63 	PROP_CHANNELMIXER_RED,
64 	PROP_CHANNELMIXER_GREEN,
65 	PROP_CHANNELMIXER_BLUE,
66 	PROP_RECALC_TEMP
67 };
68 
69 static void
rs_settings_class_init(RSSettingsClass * klass)70 rs_settings_class_init (RSSettingsClass *klass)
71 {
72 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
73 	object_class->finalize = rs_settings_finalize;
74 	object_class->get_property = get_property;
75 	object_class->set_property = set_property;
76 
77 	g_object_class_install_property(object_class,
78 		PROP_EXPOSURE, g_param_spec_float(
79 	/* @TRANSLATORS: "Expos" is short version of "Exposure". You cannot use more than 5 characters for this! */
80 			"exposure", _("Expos"), _("Exposure Compensation"),
81 			-3.0, 3.0, 0.0, G_PARAM_READWRITE)
82 	);
83 	g_object_class_install_property(object_class,
84 		PROP_SATURATION, g_param_spec_float(
85 			/* @TRANSLATORS: "Satur" is short version of "Saturation". You cannot use more than 5 characters for this! */
86 			"saturation", _("Satur"), _("Saturation"),
87 			0.0, 2.0, 1.0, G_PARAM_READWRITE)
88 	);
89 	g_object_class_install_property(object_class,
90 		PROP_HUE, g_param_spec_float(
91 			/* @TRANSLATORS: You cannot use more than 5 characters for "Hue" */
92 			"hue", _("Hue"), _("Hue Shift"),
93 			-180.0, 180.0, 0.0, G_PARAM_READWRITE)
94 	);
95 	g_object_class_install_property(object_class,
96 		PROP_CONTRAST, g_param_spec_float(
97 			/* @TRANSLATORS: "Contr" is short version of "Contrast". You cannot use more than 5 characters for this! */
98 			"contrast", _("Contr"), _("Contrast"),
99 			0.5, 2.5, 1.0, G_PARAM_READWRITE)
100 	);
101 	g_object_class_install_property(object_class,
102 		PROP_WARMTH, g_param_spec_float(
103 			/* @TRANSLATORS: "Temp" is short version of "Temperature". You cannot use more than 5 characters for this! */
104 			"warmth", _("Temp"), _("Temperature"),
105 			-1.0, 1.0, 0.0, G_PARAM_READWRITE)
106 	);
107 	g_object_class_install_property(object_class,
108 		PROP_TINT, g_param_spec_float(
109 			/* @TRANSLATORS: You cannot use more than 5 characters for "Tint" */
110 			"tint", _("Tint"), _("Tint Shift"),
111 			-2.0, 2.0, 0.0, G_PARAM_READWRITE)
112 	);
113 	g_object_class_install_property(object_class,
114 		PROP_DCP_TEMP, g_param_spec_float(
115 			/* @TRANSLATORS: "Temp" is short version of "Temperature". You cannot use more than 5 characters for this! */
116 			"dcp-temp", _("Temp"), _("Temperature"),
117 			2000.0, 12000.0, 5000.0, G_PARAM_READWRITE)
118 	);
119 	g_object_class_install_property(object_class,
120 		PROP_DCP_TINT, g_param_spec_float(
121 			/* @TRANSLATORS: You cannot use more than 5 characters for "Tint" */
122 			"dcp-tint", _("Tint"), _("Tint Shift"),
123 			-150.0, 150.0, 0.0, G_PARAM_READWRITE)
124 	);
125 	g_object_class_install_property(object_class,
126 		PROP_WB_ASCII, g_param_spec_string(
127 			"wb_ascii", _("WBAscii"), _("WBAscii"),
128 			NULL, G_PARAM_READWRITE)
129 	);
130 	g_object_class_install_property(object_class,
131 		PROP_SHARPEN, g_param_spec_float(
132 			/* @TRANSLATORS: "Sharp" is short version of "Sharpen". You cannot use more than 5 characters for this! */
133 			"sharpen", _("Sharp"), _("Sharpen Amount"),
134 			0.0, 100.0, 0.0, G_PARAM_READWRITE)
135 	);
136 	g_object_class_install_property(object_class,
137 		PROP_DENOISE_LUMA, g_param_spec_float(
138 			/* @TRANSLATORS: "Denoi" is short version of "Denoise". You cannot use more than 5 characters for this! */
139 			"denoise_luma", _("Denoi"), _("Light Denoising"),
140 			0.0, 100.0, 0.0, G_PARAM_READWRITE)
141 	);
142 	g_object_class_install_property(object_class,
143 		PROP_DENOISE_CHROMA, g_param_spec_float(
144 			/* @TRANSLATORS: "ColDn" is short version of "Colour Denoise". You cannot use more than 5 characters for this! */
145 			"denoise_chroma", _("ColDn"), _("Colour Denoising"),
146 			0.0, 100.0, 0.0, G_PARAM_READWRITE)
147 	);
148 	g_object_class_install_property(object_class,
149 		PROP_TCA_KR, g_param_spec_float(
150 			/* @TRANSLATORS: "CA R" is short version of "Chromatic Aberration Red". You cannot use more than 5 characters for this! */
151 			"tca_kr", _("CA R"), _("Red Chromatic Aberration Correction"),
152 			-0.5, 0.5, 0.0, G_PARAM_READWRITE)
153 	);
154 	g_object_class_install_property(object_class,
155 		PROP_TCA_KB, g_param_spec_float(
156 			/* @TRANSLATORS: "CA B" is short version of "Chromatic Aberration Blue". You cannot use more than 5 characters for this! */
157 			"tca_kb", _("CA B"), _("Blue Chromatic Aberration Correction"),
158 			-0.5, 0.5, 0.0, G_PARAM_READWRITE)
159 	);
160 	g_object_class_install_property(object_class,
161 		PROP_VIGNETTING, g_param_spec_float(
162 			/* @TRANSLATORS: "Vign" is short version of "Vignetting". You cannot use more than 5 characters for this! */
163 			"vignetting", _("Vign"), _("Vignetting Correction"),
164 			-1.0, 1.0, 0.0, G_PARAM_READWRITE)
165 	);
166 	g_object_class_install_property(object_class,
167 		PROP_CHANNELMIXER_RED, g_param_spec_float(
168 			/* @TRANSLATORS: You cannot use more than 5 characters for "Red" */
169 			"channelmixer_red", _("Red"), _("Red Amount Adjustment"),
170 			0.0, 300.0, 100.0, G_PARAM_READWRITE)
171 	);
172 	g_object_class_install_property(object_class,
173 		PROP_CHANNELMIXER_GREEN, g_param_spec_float(
174 			/* @TRANSLATORS: You cannot use more than 5 characters for "Green" */
175 			"channelmixer_green", _("Green"), _("Green Amount Adjustment"),
176 			0.0, 300.0, 100.0, G_PARAM_READWRITE)
177 	);
178 	g_object_class_install_property(object_class,
179 		PROP_CHANNELMIXER_BLUE, g_param_spec_float(
180 			/* @TRANSLATORS: You cannot use more than 5 characters for "Blue" */
181 			"channelmixer_blue", _("Blue"), _("Blue Amount Adjustment"),
182 			0.0, 300.0, 100.0, G_PARAM_READWRITE)
183 	);
184 	g_object_class_install_property(object_class,
185 		PROP_RECALC_TEMP, g_param_spec_boolean(
186 			"recalc-temp", "recalc-temp", "Recalculate Temperature",
187 			FALSE, G_PARAM_READWRITE)
188 	);
189 
190 	signals[SETTINGS_CHANGED] = g_signal_new ("settings-changed",
191 		G_TYPE_FROM_CLASS (klass),
192 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
193 		0, /* Is this right? */
194 		NULL,
195 		NULL,
196 		g_cclosure_marshal_VOID__INT,
197 		G_TYPE_NONE, 1, G_TYPE_INT);
198 	signals[WB_CHANGED] = g_signal_new ("wb-recalculated",
199 		G_TYPE_FROM_CLASS (klass),
200 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
201 		0, /* Is this right? */
202 		NULL,
203 		NULL,
204 		g_cclosure_marshal_VOID__VOID,
205 		G_TYPE_NONE, 0);
206 }
207 
208 static void
rs_settings_init(RSSettings * self)209 rs_settings_init (RSSettings *self)
210 {
211 	self->commit = 0;
212 	self->commit_todo = 0;
213 	self->curve_knots = NULL;
214 	self->wb_ascii = NULL;
215 	rs_settings_reset(self, MASK_ALL);
216 }
217 
218 RSSettings *
rs_settings_new(void)219 rs_settings_new (void)
220 {
221 	return g_object_new (RS_TYPE_SETTINGS, NULL);
222 }
223 
224 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)225 get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
226 {
227 	RSSettings *settings = RS_SETTINGS(object);
228 
229 #define CASE(upper, lower) \
230 	case PROP_##upper: \
231 		g_value_set_float(value, settings->lower); \
232 		break
233 	switch (property_id)
234 	{
235 		CASE(EXPOSURE, exposure);
236 		CASE(SATURATION, saturation);
237 		CASE(HUE, hue);
238 		CASE(CONTRAST, contrast);
239 		CASE(WARMTH, warmth);
240 		CASE(TINT, tint);
241 		CASE(DCP_TEMP, dcp_temp);
242 		CASE(DCP_TINT, dcp_tint);
243 	case PROP_WB_ASCII:
244 		g_value_set_string(value, settings->wb_ascii);
245 		break;
246 		CASE(SHARPEN, sharpen);
247 		CASE(DENOISE_LUMA, denoise_luma);
248 		CASE(DENOISE_CHROMA, denoise_chroma);
249 		CASE(TCA_KR, tca_kr);
250 		CASE(TCA_KB, tca_kb);
251 		CASE(VIGNETTING, vignetting);
252 		CASE(CHANNELMIXER_RED, channelmixer_red);
253 		CASE(CHANNELMIXER_GREEN, channelmixer_green);
254 		CASE(CHANNELMIXER_BLUE, channelmixer_blue);
255 	case PROP_RECALC_TEMP:
256 		g_value_set_boolean(value, settings->recalc_temp);
257 		break;
258 		default:
259 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
260 	}
261 #undef CASE
262 }
263 
264 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)265 set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
266 {
267 	RSSettings *settings = RS_SETTINGS(object);
268 	RSSettingsMask changed_mask = 0;
269 
270 #define CASE(upper, lower) \
271 	case PROP_##upper: \
272 		if (settings->lower != g_value_get_float(value)) \
273 		{ \
274 			settings->lower = g_value_get_float(value); \
275 			changed_mask |= MASK_##upper; \
276 		} \
277 		break
278 	switch (property_id)
279 	{
280 		CASE(EXPOSURE, exposure);
281 		CASE(SATURATION, saturation);
282 		CASE(HUE, hue);
283 		CASE(CONTRAST, contrast);
284 	case PROP_WARMTH:
285 		if (settings->warmth != g_value_get_float(value))
286 		{
287 			settings->warmth = g_value_get_float(value);
288 			changed_mask |= MASK_WARMTH;
289 			g_object_set(settings, "wb_ascii", NULL, NULL);
290 		}
291 		break;
292 	case PROP_TINT:
293 		if (settings->tint != g_value_get_float(value))
294 		{
295 			settings->tint = g_value_get_float(value);
296 			changed_mask |= MASK_TINT;
297 			g_object_set(settings, "wb_ascii", NULL, NULL);
298 		}
299 		break;
300 	case PROP_DCP_TEMP:
301 		if (settings->dcp_temp != g_value_get_float(value))
302 		{
303 			settings->dcp_temp = g_value_get_float(value);
304 			changed_mask |= MASK_WARMTH;
305 			g_object_set(settings, "wb_ascii", NULL, NULL);
306 		}
307 		break;
308 	case PROP_DCP_TINT:
309 		if (settings->dcp_tint != g_value_get_float(value))
310 		{
311 			settings->dcp_tint = g_value_get_float(value);
312 			changed_mask |= MASK_TINT;
313 			g_object_set(settings, "wb_ascii", NULL, NULL);
314 		}
315 		break;
316 	case PROP_WB_ASCII:
317 		if (settings->wb_ascii)
318 			g_free(settings->wb_ascii);
319 		settings->wb_ascii = g_strdup(g_value_get_string(value));
320 		changed_mask |= MASK_WB;
321 		break;
322 		CASE(SHARPEN, sharpen);
323 		CASE(DENOISE_LUMA, denoise_luma);
324 		CASE(DENOISE_CHROMA, denoise_chroma);
325 		CASE(TCA_KR, tca_kr);
326 		CASE(TCA_KB, tca_kb);
327 		CASE(VIGNETTING, vignetting);
328 		CASE(CHANNELMIXER_RED, channelmixer_red);
329 		CASE(CHANNELMIXER_GREEN, channelmixer_green);
330 		CASE(CHANNELMIXER_BLUE, channelmixer_blue);
331 		case PROP_RECALC_TEMP:
332 			settings->recalc_temp = g_value_get_boolean(value);
333 			if (settings->recalc_temp)
334 				changed_mask |= MASK_WB;
335 			break;
336 		default:
337 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
338 	}
339 #undef CASE
340 
341 	if (changed_mask > 0)
342 	{
343 		if (settings->commit > 0)
344 			settings->commit_todo |= changed_mask;
345 		else
346 			g_signal_emit(settings, signals[SETTINGS_CHANGED], 0, changed_mask);
347 	}
348 }
349 
350 /**
351  * Reset a RSSettings
352  * @param settings A RSSettings
353  * @param mask A mask for only resetting some values
354  */
355 void
rs_settings_reset(RSSettings * settings,const RSSettingsMask mask)356 rs_settings_reset(RSSettings *settings, const RSSettingsMask mask)
357 {
358 	g_assert(RS_IS_SETTINGS(settings));
359 	GObject *object = G_OBJECT(settings);
360 
361 	rs_settings_commit_start(settings);
362 
363 	if (mask & MASK_EXPOSURE)
364 		rs_object_class_property_reset(object, "exposure");
365 
366 	if (mask & MASK_SATURATION)
367 		rs_object_class_property_reset(object, "saturation");
368 
369 	if (mask & MASK_HUE)
370 		rs_object_class_property_reset(object, "hue");
371 
372 	if (mask & MASK_CONTRAST)
373 		rs_object_class_property_reset(object, "contrast");
374 
375 	if (mask & MASK_WARMTH)
376 		rs_object_class_property_reset(object, "warmth");
377 
378 	if (mask & MASK_TINT)
379 		rs_object_class_property_reset(object, "tint");
380 
381 	if (mask & MASK_WARMTH)
382 		rs_object_class_property_reset(object, "dcp-temp");
383 
384 	if (mask & MASK_TINT)
385 		rs_object_class_property_reset(object, "dcp-tint");
386 
387 	if (mask & MASK_SHARPEN)
388 		rs_object_class_property_reset(object, "sharpen");
389 
390 	if (mask & MASK_DENOISE_LUMA)
391 		rs_object_class_property_reset(object, "denoise_luma");
392 
393 	if (mask & MASK_DENOISE_CHROMA)
394 		rs_object_class_property_reset(object, "denoise_chroma");
395 
396 	if (mask & MASK_TCA_KR)
397 		rs_object_class_property_reset(object, "tca_kr");
398 
399 	if (mask & MASK_TCA_KB)
400 		rs_object_class_property_reset(object, "tca_kb");
401 
402 	if (mask & MASK_VIGNETTING)
403 		rs_object_class_property_reset(object, "vignetting");
404 
405 	if (mask & MASK_CHANNELMIXER_RED)
406 		rs_object_class_property_reset(object, "channelmixer_red");
407 
408 	if (mask & MASK_CHANNELMIXER_GREEN)
409 		rs_object_class_property_reset(object, "channelmixer_green");
410 
411 	if (mask & MASK_CHANNELMIXER_BLUE)
412 		rs_object_class_property_reset(object, "channelmixer_blue");
413 
414 	if (mask && MASK_CURVE)
415 	{
416 		if (settings->curve_knots)
417 			g_free(settings->curve_knots);
418 		settings->curve_knots = g_new(gfloat, 4);
419 		settings->curve_knots[0] = 0.0;
420 		settings->curve_knots[1] = 0.0;
421 		settings->curve_knots[2] = 1.0;
422 		settings->curve_knots[3] = 1.0;
423 		settings->curve_nknots = 2;
424 		settings->commit_todo |= MASK_CURVE;
425 	}
426 	rs_settings_commit_stop(settings);
427 }
428 
429 /**
430  * Stop signal emission from a RSSettings and queue up signals
431  * @param settings A RSSettings
432  */
433 void
rs_settings_commit_start(RSSettings * settings)434 rs_settings_commit_start(RSSettings *settings)
435 {
436 	g_assert(RS_IS_SETTINGS(settings));
437 	g_assert(settings->commit >= 0);
438 
439 	/* If we have no current commit running, reset todo */
440 	if (settings->commit == 0)
441 		settings->commit_todo = 0;
442 
443 	/* Increment commit */
444 	settings->commit++;
445 }
446 
447 /**
448  * Restart signal emission and process signal queue if any
449  * @param settings A RSSettings
450  * @return The mask of changes since rs_settings_commit_start()
451  */
452 RSSettingsMask
rs_settings_commit_stop(RSSettings * settings)453 rs_settings_commit_stop(RSSettings *settings)
454 {
455 	g_assert(RS_IS_SETTINGS(settings));
456 	g_assert(settings->commit >= 0);
457 
458 	/* If this is the last nested commit, do the todo */
459 	if ((settings->commit == 1) && (settings->commit_todo != 0))
460 	{
461 		g_signal_emit(settings, signals[SETTINGS_CHANGED], 0, settings->commit_todo);
462 	}
463 
464 	/* Make sure we never go below 0 */
465 	settings->commit = MAX(settings->commit-1, 0);
466 
467 	return settings->commit_todo;
468 }
469 
470 /**
471  * Copy settings from one RSSettins to another
472  * @param source The source RSSettings
473  * @param mask A RSSettingsMask to do selective copying
474  * @param target The target RSSettings
475  */
476 RSSettingsMask
rs_settings_copy(RSSettings * source,RSSettingsMask mask,RSSettings * target)477 rs_settings_copy(RSSettings *source, RSSettingsMask mask, RSSettings *target)
478 {
479 	RSSettingsMask changed_mask = 0;
480 
481 	g_assert(RS_IS_SETTINGS(source));
482 	g_assert(RS_IS_SETTINGS(target));
483 
484 	/* Convenience macro */
485 #define SETTINGS_COPY(upper, lower) \
486 do { \
487 	if ((mask & MASK_##upper) && (target->lower != source->lower)) \
488 	{ \
489 		changed_mask |= MASK_ ##upper; \
490 		target->lower = source->lower; \
491 	} \
492 } while(0)
493 
494 	if ((mask & MASK_WB) && (g_strcmp0(target->wb_ascii, source->wb_ascii) != 0))
495 	{
496 		if (target->wb_ascii)
497 			g_free(target->wb_ascii);
498 
499 		changed_mask |= MASK_WB; \
500 		target->wb_ascii = g_strdup(source->wb_ascii);
501 	}
502 	SETTINGS_COPY(EXPOSURE, exposure);
503 	SETTINGS_COPY(SATURATION, saturation);
504 	SETTINGS_COPY(HUE, hue);
505 	SETTINGS_COPY(CONTRAST, contrast);
506 	SETTINGS_COPY(WARMTH, warmth);
507 	SETTINGS_COPY(TINT, tint);
508 	SETTINGS_COPY(DCP_TEMP, dcp_temp);
509 	SETTINGS_COPY(DCP_TINT, dcp_tint);
510 	SETTINGS_COPY(SHARPEN, sharpen);
511 	SETTINGS_COPY(DENOISE_LUMA, denoise_luma);
512 	SETTINGS_COPY(DENOISE_CHROMA, denoise_chroma);
513 	SETTINGS_COPY(TCA_KR, tca_kr);
514 	SETTINGS_COPY(TCA_KB, tca_kb);
515 	SETTINGS_COPY(VIGNETTING, vignetting);
516 	SETTINGS_COPY(CHANNELMIXER_RED, channelmixer_red);
517 	SETTINGS_COPY(CHANNELMIXER_GREEN, channelmixer_green);
518 	SETTINGS_COPY(CHANNELMIXER_BLUE, channelmixer_blue);
519 #undef SETTINGS_COPY
520 
521 	if (mask & MASK_WB)
522 		target->recalc_temp = source->recalc_temp;
523 
524 	if (mask & MASK_CURVE)
525 	{
526 		/* Check if we actually have changed */
527 		if (target->curve_nknots != source->curve_nknots)
528 			changed_mask |= MASK_CURVE;
529 		else
530 		{
531 			if (memcmp(source->curve_knots, target->curve_knots, sizeof(gfloat)*2*source->curve_nknots)!=0)
532 				changed_mask |= MASK_CURVE;
533 		}
534 
535 		/* Copy the knots if needed */
536 		if (changed_mask & MASK_CURVE)
537 		{
538 			g_free(target->curve_knots);
539 			target->curve_knots = g_memdup(source->curve_knots, sizeof(gfloat)*2*source->curve_nknots);
540 			target->curve_nknots = source->curve_nknots;
541 		}
542 	}
543 
544 	/* Emit seignal if needed */
545 	if (changed_mask > 0)
546 		g_signal_emit(target, signals[SETTINGS_CHANGED], 0, changed_mask);
547 
548 	return changed_mask;
549 }
550 
551 /**
552  * Set curve knots
553  * @param settings A RSSettings
554  * @param knots Knots for curve
555  * @param nknots Number of knots
556  */
557 void
rs_settings_set_curve_knots(RSSettings * settings,const gfloat * knots,const gint nknots)558 rs_settings_set_curve_knots(RSSettings *settings, const gfloat *knots, const gint nknots)
559 {
560 	g_assert(RS_IS_SETTINGS(settings));
561 	g_assert(nknots > 0);
562 	g_assert(knots != NULL);
563 
564 	g_free(settings->curve_knots);
565 
566 	settings->curve_knots = g_memdup(knots, sizeof(gfloat)*2*nknots);
567 	settings->curve_nknots = nknots;
568 
569 	g_signal_emit(settings, signals[SETTINGS_CHANGED], 0, MASK_CURVE);
570 }
571 
572 /**
573  * Set the warmth and tint values of a RSSettings
574  * @param settings A RSSettings
575  * @param exposure New value
576  */
577 void
rs_settings_set_wb(RSSettings * settings,const gfloat warmth,const gfloat tint,const gchar * ascii)578 rs_settings_set_wb(RSSettings *settings, const gfloat warmth, const gfloat tint, const gchar *ascii)
579 {
580 	g_assert(RS_IS_SETTINGS(settings));
581 
582 	rs_settings_commit_start(settings);
583 	g_object_set(settings, "warmth", warmth, "tint", tint, "wb_ascii", ascii, "recalc-temp", TRUE, NULL);
584 	rs_settings_commit_stop(settings);
585 }
586 
587 /**
588  * Get the knots from the curve
589  * @param settings A RSSettings
590  * @return All knots as a newly allocated array
591  */
592 gfloat *
rs_settings_get_curve_knots(RSSettings * settings)593 rs_settings_get_curve_knots(RSSettings *settings)
594 {
595 	g_assert(RS_IS_SETTINGS(settings));
596 
597 	return g_memdup(settings->curve_knots, sizeof(gfloat)*2*settings->curve_nknots);
598 }
599 
600 /**
601  * Get number of knots in curve in a RSSettings
602  * @param settings A RSSettings
603  * @return Number of knots
604  */
605 gint
rs_settings_get_curve_nknots(RSSettings * settings)606 rs_settings_get_curve_nknots(RSSettings *settings)
607 {
608 	g_assert(RS_IS_SETTINGS(settings));
609 
610 	return settings->curve_nknots;
611 }
612 
613 /**
614  * Link two RSSettings together, if source gets updated, it will propagate to target
615  * @param source A RSSettings
616  * @param target A RSSettings
617  */
618 void
rs_settings_link(RSSettings * source,RSSettings * target)619 rs_settings_link(RSSettings *source, RSSettings *target)
620 {
621 	g_assert(RS_IS_SETTINGS(source));
622 	g_assert(RS_IS_SETTINGS(target));
623 
624 	/* Add a weak reference to target, we would really like to know if it disappears */
625 	g_object_weak_ref(G_OBJECT(target), (GWeakNotify) rs_settings_unlink, source);
626 
627 	/* Use glib signals to propagate changes */
628 	g_signal_connect(source, "settings-changed", G_CALLBACK(rs_settings_copy), target);
629 }
630 
631 /**
632  * Unlink two RSSettings - this will be done automaticly if target from a
633  * previous rs_settings_link() is finalized
634  * @param source A RSSettings
635  * @param target A RSSettings - can be destroyed, doesn't matter, we just need the pointer
636  */
637 void
rs_settings_unlink(RSSettings * source,RSSettings * target)638 rs_settings_unlink(RSSettings *source, RSSettings *target)
639 {
640 	gulong signal_id;
641 
642 	g_assert(RS_IS_SETTINGS(source));
643 
644 	/* If we can find a signal linking these two pointers, disconnect it */
645 	signal_id = g_signal_handler_find(source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, target);
646 	if (signal_id > 0)
647 		g_signal_handler_disconnect(source, signal_id);
648 }
649