1 /*
2  * This file is part of Siril, an astronomy image processor.
3  * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4  * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5  * Reference site is https://free-astro.org/index.php/Siril
6  *
7  * Siril is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Siril is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <string.h>
26 
27 #include "core/siril.h"
28 #include "gui/utils.h"
29 #include "gui/callbacks.h"
30 #include "gui/image_display.h"
31 #include "gui/histogram.h"
32 #include "gui/progress_and_log.h"
33 #include "io/single_image.h"
34 #include "io/image_format_fits.h"
35 #include "core/undo.h"
36 #include "core/proto.h"
37 #include "algos/statistics.h"
38 
39 #ifndef O_BINARY
40 #define O_BINARY 0
41 #endif
42 
43 /* *filename must be freed */
undo_build_swapfile(fits * fit,char ** filename)44 static int undo_build_swapfile(fits *fit, char **filename) {
45 	gchar *nameBuff;
46 	char name[] = "siril_swp-XXXXXX";
47 	gchar *tmpdir;
48 	int fd;
49 
50 	tmpdir = com.pref.swap_dir;
51 	nameBuff = g_build_filename(tmpdir, name, NULL);
52 	fd = g_mkstemp(nameBuff);
53 	if (fd < 1) {
54 		siril_log_message(_("File I/O Error: Unable to create swap file in %s: [%s]\n"),
55 				tmpdir, strerror(errno));
56 		g_free(nameBuff);
57 		return 1;
58 	}
59 
60 	size_t size = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
61 
62 	errno = 0;
63 	// Write some data to the temporary file
64 	if (fit->type == DATA_USHORT) {
65 		if (-1 == write(fd, fit->data, size * sizeof(WORD))) {
66 			siril_log_message(_("File I/O Error: Unable to write swap file in %s: [%s]\n"),
67 					tmpdir, strerror(errno));
68 			g_free(nameBuff);
69 			g_close(fd, NULL);
70 			return 1;
71 		}
72 	} else if (fit->type == DATA_FLOAT) {
73 		if (-1 == write(fd, fit->fdata, size * sizeof(float))) {
74 			siril_log_message(_("File I/O Error: Unable to write swap file in %s: [%s]\n"),
75 					tmpdir, strerror(errno));
76 			g_free(nameBuff);
77 			g_close(fd, NULL);
78 			return 1;
79 		}
80 	}
81 	*filename = nameBuff;
82 	g_close(fd, NULL);
83 
84 	return 0;
85 }
86 
undo_remove_item(historic * histo,int index)87 static int undo_remove_item(historic *histo, int index) {
88 	if (histo[index].filename) {
89 		g_unlink(histo[index].filename);
90 		g_free(histo[index].filename);
91 		histo[index].filename = NULL;
92 	}
93 	memset(histo[index].history, 0, FLEN_VALUE);
94 	return 0;
95 }
96 
undo_add_item(fits * fit,char * filename,char * histo)97 static void undo_add_item(fits *fit, char *filename, char *histo) {
98 
99 	if (!com.history) {
100 		com.hist_size = HISTORY_SIZE;
101 		com.history = calloc(com.hist_size, sizeof(historic));
102 		com.hist_current = 0;
103 		com.hist_display = 0;
104 	}
105 	/* when undo, we remove all further items being after */
106 	while (com.hist_display < com.hist_current) {
107 		com.hist_current--;
108 		undo_remove_item(com.history, com.hist_current);
109 	}
110 	com.history[com.hist_current].filename = filename;
111 	com.history[com.hist_current].rx = fit->rx;
112 	com.history[com.hist_current].ry = fit->ry;
113 	com.history[com.hist_current].type = fit->type;
114 	snprintf(com.history[com.hist_current].history, FLEN_VALUE, "%s", histo);
115 
116 	if (com.hist_current == com.hist_size - 1) {
117 		/* we must shift all elements except 0 that must always match with the original file
118 		 * 0  1  2  3  4  5  6  7  8  9 10 become
119 		 * 0  2  3  4  5  6  7  8  9 10 11 and
120 		 * 0  3  4  5  6  7  8  9 10 11 12 and so on
121 		 */
122 		undo_remove_item(com.history, 1);
123 		memmove(&com.history[1], &com.history[2],
124 				(com.hist_size - 2) * sizeof(*com.history));
125 		com.hist_current = com.hist_size - 2;
126 	}
127 	com.hist_current++;
128 	com.hist_display = com.hist_current;
129 }
130 
undo_get_data_ushort(fits * fit,historic hist)131 static int undo_get_data_ushort(fits *fit, historic hist) {
132 	int fd;
133 
134 	if ((fd = g_open(hist.filename, O_RDONLY | O_BINARY, 0)) == -1) {
135 		printf("Error opening swap file : %s\n", hist.filename);
136 		return 1;
137 	}
138 
139 	errno = 0;
140 	fit->rx = fit->naxes[0] = hist.rx;
141 	fit->ry = fit->naxes[1] = hist.ry;
142 	/* TODO: fit->naxes[0] = fit->rx ? what about naxes[2] ? */
143 
144 	size_t n = fit->naxes[0] * fit->naxes[1];
145 	size_t size = n * fit->naxes[2] * sizeof(WORD);
146 	WORD *buf = calloc(1, size);
147 	// read the data from temporary file
148 	if ((read(fd, buf, size)) < size) {
149 		printf("Undo Read of [%s], failed with error [%s]\n", hist.filename, strerror(errno));
150 		free(buf);
151 		g_close(fd, NULL);
152 		return 1;
153 	}
154 	/* need to reallocate data as size may have changed */
155 	WORD *newdata = (WORD*) realloc(fit->data, size);
156 	if (!newdata) {
157 		PRINT_ALLOC_ERR;
158 		free(newdata);
159 		free(buf);
160 		g_close(fd, NULL);
161 		return 1;
162 	}
163 	fit->data = newdata;
164 	memcpy(fit->data, buf, size);
165 	fit->pdata[RLAYER] = fit->data;
166 	if (fit->naxes[2] > 1) {
167 		fit->pdata[GLAYER] = fit->data + n;
168 		fit->pdata[BLAYER] = fit->data + n * 2;
169 	}
170 	full_stats_invalidation_from_fit(fit);
171 	free(buf);
172 	g_close(fd, NULL);
173 	return 0;
174 }
175 
undo_get_data_float(fits * fit,historic hist)176 static int undo_get_data_float(fits *fit, historic hist) {
177 	int fd;
178 
179 	if ((fd = g_open(hist.filename, O_RDONLY | O_BINARY, 0)) == -1) {
180 		printf("Error opening swap file : %s\n", hist.filename);
181 		return 1;
182 	}
183 
184 	errno = 0;
185 	fit->rx = fit->naxes[0] = hist.rx;
186 	fit->ry = fit->naxes[1] = hist.ry;
187 
188 	size_t n = fit->naxes[0] * fit->naxes[1];
189 	size_t size = n * fit->naxes[2] * sizeof(float);
190 	float *buf = calloc(1, size);
191 	// read the data from temporary file
192 	if ((read(fd, buf, size) < size)) {
193 		printf("Undo Read of [%s], failed with error [%s]\n", hist.filename, strerror(errno));
194 		free(buf);
195 		g_close(fd, NULL);
196 		return 1;
197 	}
198 	/* need to reallocate data as size may have changed */
199 	float *newdata = (float*) realloc(fit->data, size);
200 	if (!newdata) {
201 		PRINT_ALLOC_ERR;
202 		free(newdata);
203 		free(buf);
204 		g_close(fd, NULL);
205 		return 1;
206 	}
207 	fit->fdata = newdata;
208 	memcpy(fit->fdata, buf, size);
209 	fit->fpdata[RLAYER] = fit->fdata;
210 	if (fit->naxes[2] > 1) {
211 		fit->fpdata[GLAYER] = fit->fdata + n;
212 		fit->fpdata[BLAYER] = fit->fdata + n * 2;
213 	}
214 	full_stats_invalidation_from_fit(fit);
215 	free(buf);
216 	g_close(fd, NULL);
217 	return 0;
218 }
219 
undo_get_data(fits * fit,historic hist)220 static int undo_get_data(fits *fit, historic hist) {
221 	if (hist.type == DATA_USHORT) {
222 		if (gfit.type != DATA_USHORT) {
223 			size_t ndata = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
224 			fit_replace_buffer(fit, float_buffer_to_ushort(fit->fdata, ndata), DATA_USHORT);
225 			set_precision_switch();
226 		}
227 		return undo_get_data_ushort(fit, hist);
228 	} else if (hist.type == DATA_FLOAT) {
229 		if (gfit.type != DATA_FLOAT) {
230 			size_t ndata = fit->naxes[0] * fit->naxes[1] * fit->naxes[2];
231 			fit_replace_buffer(fit, ushort_buffer_to_float(fit->data, ndata), DATA_FLOAT);
232 			set_precision_switch();
233 		}
234 		return undo_get_data_float(fit, hist);
235 	}
236 	return 1;
237 }
238 
is_undo_available()239 gboolean is_undo_available() {
240     return (com.history && com.hist_display > 0);
241 }
242 
is_redo_available()243 gboolean is_redo_available() {
244     return (com.history && (com.hist_display < com.hist_current - 1));
245 }
246 
undo_save_state(fits * fit,char * message,...)247 int undo_save_state(fits *fit, char *message, ...) {
248 	gchar *filename;
249 	char histo[FLEN_VALUE] = { 0 };
250 	va_list args;
251 	va_start(args, message);
252 
253 	if (single_image_is_loaded()) {
254 		if (message != NULL) {
255 			vsnprintf(histo, FLEN_VALUE, message, args);
256 		}
257 
258 		if (undo_build_swapfile(fit, &filename)) {
259 			va_end(args);
260 			return 1;
261 		}
262 
263 		undo_add_item(fit, filename, histo);
264 
265 		/* update menus */
266 		update_MenuItem();
267 	}
268 	va_end(args);
269 	return 0;
270 }
271 
undo_display_data(int dir)272 int undo_display_data(int dir) {
273 	if (!com.history) {
274 		return 1;
275 	}
276 	switch (dir) {
277 	case UNDO:
278 		if (is_undo_available()) {
279 			if (com.hist_current == com.hist_display) {
280 				undo_save_state(&gfit, NULL);
281 				com.hist_display--;
282 			}
283 			com.hist_display--;
284 			siril_log_message(_("Undo: %s\n"), com.history[com.hist_display].history);
285 			undo_get_data(&gfit, com.history[com.hist_display]);
286 			invalidate_gfit_histogram();
287 			invalidate_stats_from_fit(&gfit);
288 			update_gfit_histogram_if_needed();
289 			update_MenuItem();
290 			redraw(com.cvport, REMAP_ALL);
291 		}
292 		break;
293 	case REDO:
294 		if (is_redo_available()) {
295 			siril_log_message(_("Redo: %s\n"), com.history[com.hist_display].history);
296 			com.hist_display++;
297 			undo_get_data(&gfit, com.history[com.hist_display]);
298 			invalidate_gfit_histogram();
299 			invalidate_stats_from_fit(&gfit);
300 			update_gfit_histogram_if_needed();
301 			update_MenuItem();
302 			redraw(com.cvport, REMAP_ALL);
303 		}
304 		break;
305 	default:
306 		printf("ERROR\n");
307 		return -1;
308 	}
309 	return 0;
310 }
311 
undo_flush()312 int undo_flush() {
313 	if (!com.history) {
314 		return 1;
315 	}
316 	for (int i = 0; i < com.hist_current; i++) {
317 		undo_remove_item(com.history, i);
318 	}
319 	free(com.history);
320 	com.history = NULL;
321 	com.hist_current = 0;
322 	com.hist_display = 0;
323 	return 0;
324 }
325 
set_undo_redo_tooltip()326 void set_undo_redo_tooltip() {
327 	if (is_undo_available()) {
328 		gchar *str = g_strdup_printf(_("Undo: \"%s\""), com.history[com.hist_display - 1].history);
329 		gtk_widget_set_tooltip_text(lookup_widget("header_undo_button"), str);
330 		g_free(str);
331 	}
332 	else gtk_widget_set_tooltip_text(lookup_widget("header_undo_button"), _("Nothing to undo"));
333 	if (is_redo_available()) {
334 		gchar *str = g_strdup_printf(_("Redo: \"%s\""), com.history[com.hist_display].history);
335 		gtk_widget_set_tooltip_text(lookup_widget("header_redo_button"), str);
336 		g_free(str);
337 	}
338 	else gtk_widget_set_tooltip_text(lookup_widget("header_redo_button"), _("Nothing to redo"));
339 }
340