1 /* Bluefish HTML Editor
2  * image.c - the thumbnail/multi-thumbnail dialogs
3  *
4  * Copyright (C) 2003-2012 Olivier Sessink
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* indented with indent -psl -ts4 -kr -l110   */
20 
21 /*#define DEBUG */
22 
23 #include <string.h>
24 
25 #include "image.h"
26 #include "cap.h"
27 #include "htmlbar.h"
28 #include "html_diag.h"
29 #include "../bf_lib.h"
30 #include "../dialog_utils.h"
31 #include "../document.h"
32 #include "../file.h"
33 #include "../gtk_easy.h"
34 #include "../stringlist.h"
35 
36 static GdkPixbufLoader *
pbloader_from_filename(const gchar * filename)37 pbloader_from_filename(const gchar * filename)
38 {
39 	GdkPixbufLoader *pbloader;
40 	GError *error = NULL;
41 	if (filename) {
42 		gchar *ext = strrchr(filename, '.');
43 		if (ext) {
44 			gchar *tmp2 = g_utf8_strdown(ext + 1, -1);
45 			if (strcmp(tmp2, "jpg") == 0) {
46 				pbloader = gdk_pixbuf_loader_new_with_type("jpeg", &error);
47 			} else {
48 				pbloader = gdk_pixbuf_loader_new_with_type(tmp2, &error);
49 			}
50 			if (error) {
51 				pbloader = gdk_pixbuf_loader_new();	/* try to guess from the data */
52 				g_error_free(error);
53 			}
54 			g_free(tmp2);
55 			return pbloader;
56 		}
57 	}
58 	return gdk_pixbuf_loader_new();
59 }
60 
61 static gchar *
create_thumbnail_filename(gchar * filename)62 create_thumbnail_filename(gchar * filename)
63 {
64 	gchar *retval, *tmp;
65 	gint len = 0, size;
66 	tmp = strrchr(filename, '.');
67 	if (tmp) {
68 		len = strlen(tmp);
69 	}
70 	size =
71 		strlen(filename) - len + strlen(main_v->props.image_thumbnailstring) +
72 		strlen(main_v->props.image_thumbnailtype) + 2;
73 	retval = g_malloc0(size * sizeof(gchar));
74 	DEBUG_MSG("create_thumbnail_filename, size=%d bytes at %p\n", size, retval);
75 	retval = strncpy(retval, filename, strlen(filename) - len);
76 	retval = strcat(retval, main_v->props.image_thumbnailstring);
77 	retval = strcat(retval, ".");
78 	retval = strcat(retval, main_v->props.image_thumbnailtype);
79 
80 	return retval;
81 }
82 
83 typedef struct {
84 	Thtml_diag *dg;
85 	GtkWidget *message;
86 	GtkWidget *frame;
87 	GdkPixbuf *pb;
88 	GtkWidget *im;
89 	GFile *full_uri;
90 
91 	GdkPixbufLoader *pbloader;
92 	Topenfile *of;
93 
94 	/* and some options for the thumbnails */
95 	GtkAdjustment *adjustment;
96 	guint adj_changed_id;
97 } Timage_diag;
98 
99 void
image_diag_destroy_cb(GtkWidget * widget,Timage_diag * imdg)100 image_diag_destroy_cb(GtkWidget * widget, Timage_diag * imdg)
101 {
102 	html_diag_destroy_cb(widget, imdg->dg);
103 	if (imdg->pb) {
104 		g_object_unref(imdg->pb);
105 	}
106 	if (imdg->full_uri) {
107 		g_object_unref(imdg->full_uri);
108 	}
109 	g_free(imdg);
110 }
111 
112 static TcheckNsave_return
async_thumbsave_lcb(TcheckNsave_status status,GError * gerror,gpointer callback_data)113 async_thumbsave_lcb(TcheckNsave_status status, GError * gerror, gpointer callback_data)
114 {
115 	DEBUG_MSG("async_thumbsave_lcb, status=%d\n", status);
116 	/* TODO: handle error */
117 	if (gerror) {
118 		g_print("failed to save thumbnail: %s\n",gerror->message);
119 	}
120 	return CHECKNSAVE_CONT;
121 }
122 
123 static void
image_insert_dialogok_lcb(GtkWidget * widget,Timage_diag * imdg)124 image_insert_dialogok_lcb(GtkWidget * widget, Timage_diag * imdg)
125 {
126 	gchar *thestring, *finalstring;
127 	gchar *thumbnailfilename, *filename;
128 	gchar *tmp1, *tmp2;
129 	gchar *buffer;
130 	gsize buflen;
131 	gint w, h;
132 	GError *error = NULL;
133 	GdkPixbuf *tmp_im;
134 	GFile *fullthumbfilename;
135 
136 	filename = gtk_editable_get_chars(GTK_EDITABLE(imdg->dg->entry[0]), 0, -1);
137 	if (strlen(filename)) {
138 		thumbnailfilename = create_thumbnail_filename(filename);
139 		/* we should use the full path to create the thumbnail filename */
140 
141 		tmp1 = g_file_get_uri(imdg->full_uri);
142 		tmp2 = create_thumbnail_filename(tmp1);
143 		fullthumbfilename = g_file_new_for_uri(tmp2);
144 
145 		g_free(tmp1);
146 		g_free(tmp2);
147 
148 #ifdef DEBUG
149 		gchar *path = g_file_get_path(fullthumbfilename);
150 		DEBUG_MSG("image_insert_dialogok_lcb, thumbnail will be stored at %s\n", path);
151 		g_free(path);
152 #endif
153 		w = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(imdg->dg->spin[0]));
154 		h = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(imdg->dg->spin[1]));
155 		tmp_im = gdk_pixbuf_scale_simple(imdg->pb, w, h, GDK_INTERP_BILINEAR);
156 
157 		if (strcmp(main_v->props.image_thumbnailtype, "jpeg") == 0) {
158 			gdk_pixbuf_save_to_buffer(tmp_im, &buffer, &buflen, main_v->props.image_thumbnailtype, &error,
159 									  "quality", "50", NULL);
160 			/* gdk_pixbuf_save(tmp_im,fullthumbfilename,main_v->props.image_thumbnailtype,&error, "quality", "50",NULL); */
161 		} else {
162 			gdk_pixbuf_save_to_buffer(tmp_im, &buffer, &buflen, main_v->props.image_thumbnailtype, &error,
163 									  NULL);
164 			/*gdk_pixbuf_save(tmp_im,fullthumbfilename,main_v->props.image_thumbnailtype,&error, NULL); */
165 		}
166 		g_object_unref(tmp_im);
167 		if (error) {
168 			g_print("ERROR while saving thumbnail to buffer: %s\n", error->message);
169 			g_error_free(error);
170 		} else {
171 			GError *error = NULL;
172 			GFileInfo *finfo;
173 			Trefcpointer *refbuf = refcpointer_new(buffer);
174 
175 			finfo = g_file_query_info(fullthumbfilename, BF_FILEINFO, G_FILE_QUERY_INFO_NONE, NULL, &error);
176 			if (error != NULL) {
177 				g_print("image_insert_dialogok_lcb: %s\n ", error->message);
178 				g_error_free(error);
179 			}
180 #ifdef DEBUG
181 			gchar *path = g_file_get_path(fullthumbfilename);
182 			DEBUG_MSG("image_insert_dialogok_lcb, starting async save to %s\n", path);
183 			g_free(path);
184 #endif
185 
186 			file_checkNsave_uri_async(fullthumbfilename, finfo, refbuf, buflen, FALSE, FALSE,
187 									  (CheckNsaveAsyncCallback) async_thumbsave_lcb, NULL, imdg->dg->bfwin);
188 			refcpointer_unref(refbuf);
189 		}
190 
191 		g_object_unref(fullthumbfilename);
192 
193 		thestring =
194 			g_strconcat(cap("<A HREF=\""), filename, cap("\"><IMG SRC=\""), thumbnailfilename, "\"", NULL);
195 		g_free(filename);
196 		g_free(thumbnailfilename);
197 
198 		thestring =
199 			insert_integer_if_spin(imdg->dg->spin[0], cap("WIDTH"), thestring,
200 								   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(imdg->dg->check[0])), 0);
201 		thestring =
202 			insert_integer_if_spin(imdg->dg->spin[1], cap("HEIGHT"), thestring,
203 								   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(imdg->dg->check[1])), 0);
204 		if (!get_curlang_option_value(imdg->dg->bfwin, lang_is_XHTML)) {
205 			thestring = insert_if_spin(imdg->dg->spin[2], cap("BORDER"), thestring, FALSE);
206 		}
207 		thestring = insert_if_spin(imdg->dg->spin[3], cap("HSPACE"), thestring, FALSE);
208 		thestring = insert_if_spin(imdg->dg->spin[4], cap("VSPACE"), thestring, FALSE);
209 		thestring = insert_string_if_entry(GTK_ENTRY(imdg->dg->entry[1]), cap("NAME"), thestring, NULL);
210 		thestring = insert_string_if_entry(GTK_ENTRY(imdg->dg->entry[2]), cap("ALT"), thestring, "");
211 		thestring = insert_string_if_entry(GTK_ENTRY(imdg->dg->entry[3]), cap("USEMAP"), thestring, NULL);
212 		thestring =
213 			insert_string_if_combobox(GTK_COMBO_BOX(imdg->dg->combo[0]), cap("ALIGN"), thestring, NULL);
214 		thestring = insert_string_if_entry(GTK_ENTRY(imdg->dg->entry[4]), NULL, thestring, NULL);
215 
216 		finalstring = g_strconcat(thestring, get_curlang_option_value(imdg->dg->bfwin, self_close_singleton_tags) ? " />" : ">", NULL);
217 		g_free(thestring);
218 
219 		if (imdg->dg->range.end == -1) {
220 			doc_insert_two_strings(imdg->dg->doc, finalstring, cap("</a>"));
221 		} else {
222 			doc_replace_text(imdg->dg->doc, finalstring, imdg->dg->range.pos, imdg->dg->range.end);
223 		}
224 
225 		g_free(finalstring);
226 	}
227 	image_diag_destroy_cb(NULL, imdg);
228 }
229 
230 void
image_diag_cancel_clicked_cb(GtkWidget * widget,gpointer data)231 image_diag_cancel_clicked_cb(GtkWidget * widget, gpointer data)
232 {
233 	image_diag_destroy_cb(NULL, data);
234 }
235 
236 static void
image_diag_finish(Timage_diag * imdg,GCallback ok_func)237 image_diag_finish(Timage_diag * imdg, GCallback ok_func)
238 {
239 	GtkWidget *align, *hbox;
240 
241 #if GTK_CHECK_VERSION(3,0,0)
242 	hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
243 #else
244 	hbox = gtk_hbutton_box_new();
245 #endif
246 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
247 	gtk_box_set_spacing(GTK_BOX(hbox), 6);
248 
249 	imdg->dg->obut = bf_stock_ok_button(ok_func, imdg);
250 	imdg->dg->cbut = bf_stock_cancel_button(G_CALLBACK(image_diag_cancel_clicked_cb), imdg);
251 
252 	gtk_box_pack_start(GTK_BOX(hbox), imdg->dg->cbut, FALSE, FALSE, 0);
253 	gtk_box_pack_start(GTK_BOX(hbox), imdg->dg->obut, FALSE, FALSE, 0);
254 	gtk_window_set_default(GTK_WINDOW(imdg->dg->dialog), imdg->dg->obut);
255 
256 	align = gtk_alignment_new(0, 0, 1, 0);
257 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 12, 0, 0, 0);
258 	gtk_container_add(GTK_CONTAINER(align), hbox);
259 	gtk_box_pack_start(GTK_BOX(imdg->dg->vbox), align, FALSE, FALSE, 0);
260 	gtk_widget_show_all(GTK_WIDGET(imdg->dg->dialog));
261 }
262 
263 static void
image_dialog_set_pixbuf(Timage_diag * imdg)264 image_dialog_set_pixbuf(Timage_diag * imdg)
265 {
266 	gfloat toobig, pb_width, pd_height;
267 	GdkPixbuf *tmp_pb;
268 	if (!imdg->pb) {
269 		return;
270 	}
271 
272 	pb_width = (gfloat)gdk_pixbuf_get_width(imdg->pb);
273 	pd_height = (gfloat)gdk_pixbuf_get_height(imdg->pb);
274 	if (imdg->dg->bfwin) {
275 		Thtmlbarsession *hbs;
276 		hbs = g_hash_table_lookup(htmlbar_v.lookup, imdg->dg->bfwin->session);
277 		toobig = pb_width / ((gfloat)hbs->thumbnailwidth);
278 		DEBUG_MSG("initialize toobig as %f, using session thumbnailwidth %d and pixbuf width %f\n",toobig,hbs->thumbnailwidth,pb_width);
279 	} else {
280 		toobig = 1.0;
281 		if ((pb_width / 250.0) > toobig) {
282 			toobig = pb_width / 250.0;
283 		}
284 
285 		if ((pd_height / 300.0) > toobig) {
286 			toobig = pd_height / 300.0;
287 		}
288 	}
289 	/* because the spin buttons in the html dialogs are designed that they can have an empty string value ""
290 	they will not accept a number as long as there is an empty string in them */
291 	gtk_entry_set_text(GTK_ENTRY(imdg->dg->spin[0]), "1");
292 	gtk_entry_set_text(GTK_ENTRY(imdg->dg->spin[1]), "1");
293 
294 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(imdg->dg->spin[0]), (pb_width / toobig));
295 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(imdg->dg->spin[1]), (pd_height / toobig));
296 	g_signal_handler_block(G_OBJECT(imdg->adjustment), imdg->adj_changed_id);
297 	gtk_adjustment_set_value(GTK_ADJUSTMENT(imdg->adjustment), 1.0 / toobig);
298 	g_signal_handler_unblock(G_OBJECT(imdg->adjustment), imdg->adj_changed_id);
299 
300 	tmp_pb =
301 		gdk_pixbuf_scale_simple(imdg->pb, (gint)(pb_width / toobig), (gint)(pd_height / toobig),
302 								main_v->globses.image_thumbnail_refresh_quality ? GDK_INTERP_BILINEAR :
303 								GDK_INTERP_NEAREST);
304 
305 	if (GTK_IS_WIDGET(imdg->im)) {
306 		DEBUG_MSG("imdg->im == %p\n", imdg->im);
307 		DEBUG_MSG("gtk_widget_destroy() %p\n", imdg->im);
308 		gtk_widget_destroy(imdg->im);
309 	}
310 
311 	imdg->im = gtk_image_new_from_pixbuf(tmp_pb);
312 
313 	g_object_unref(tmp_pb);
314 	/*gtk_container_remove(GTK_CONTAINER(imdg->frame),imdg->message); */
315 	gtk_widget_destroy(imdg->message);
316 	imdg->message = NULL;
317 
318 	gtk_container_add(GTK_CONTAINER(imdg->frame), imdg->im);
319 	gtk_widget_show(imdg->im);
320 	DEBUG_MSG("imdg->im == %p\n", imdg->im);
321 	DEBUG_MSG("image_filename_changed() finished. GTK_IS_WIDGET(imdg->im) == %d\n", GTK_IS_WIDGET(imdg->im));
322 }
323 
324 static void
image_loaded_lcb(Topenfile_status status,GError * gerror,Trefcpointer * refp,goffset buflen,gpointer callback_data)325 image_loaded_lcb(Topenfile_status status, GError * gerror, Trefcpointer * refp, goffset buflen,
326 				 gpointer callback_data)
327 {
328 	Timage_diag *imdg = callback_data;
329 	gboolean cleanup = TRUE;
330 	switch (status) {
331 	case OPENFILE_ERROR:
332 	case OPENFILE_ERROR_NOCHANNEL:
333 	case OPENFILE_ERROR_NOREAD:
334 		/* TODO: use error info in gerror */
335 		gtk_label_set_text(GTK_LABEL(imdg->message), _("Loading image failed..."));
336 		break;
337 	case OPENFILE_ERROR_CANCELLED:
338 		/* should we warn the user ?? */
339 		gdk_pixbuf_loader_close(imdg->pbloader, NULL);
340 		break;
341 	case OPENFILE_CHANNEL_OPENED:
342 		/* do nothing */
343 		cleanup = FALSE;
344 		break;
345 	case OPENFILE_FINISHED:{
346 			GError *error = NULL;
347 			if (gdk_pixbuf_loader_write(imdg->pbloader, (const guchar *) refp->data, buflen, &error)
348 				&& gdk_pixbuf_loader_close(imdg->pbloader, &error)) {
349 				imdg->pb = gdk_pixbuf_loader_get_pixbuf(imdg->pbloader);
350 				if (imdg->pb) {
351 					g_object_ref(imdg->pb);
352 					image_dialog_set_pixbuf(imdg);
353 				}
354 			}
355 		}
356 		break;
357 	}
358 	if (cleanup) {
359 		g_object_unref(imdg->pbloader);
360 		imdg->pbloader = NULL;
361 		imdg->of = NULL;
362 	}
363 }
364 
365 static void
image_filename_changed(GtkWidget * widget,Timage_diag * imdg)366 image_filename_changed(GtkWidget * widget, Timage_diag * imdg)
367 {
368 	const gchar *filename;
369 	gchar *tmp;
370 	GFile *fullfilename = NULL;
371 
372 	DEBUG_MSG("image_filename_changed() started. GTK_IS_WIDGET(imdg->im) == %d\n", GTK_IS_WIDGET(imdg->im));
373 	if (imdg->pb) {
374 		g_object_unref(imdg->pb);
375 	}
376 	if (imdg->of) {
377 		openfile_cancel(imdg->of);
378 	}
379 	DEBUG_MSG("image_filename_changed: filename=%s\n", gtk_entry_get_text(GTK_ENTRY(imdg->dg->entry[0])));
380 
381 	/* the entry usually has a relative filename, so we should make it absolute
382 	   using the basedir of the document */
383 	filename = gtk_entry_get_text(GTK_ENTRY(imdg->dg->entry[0]));
384 	/* we should use the full path to create the thumbnail filename */
385 	tmp = strstr(filename, "://");
386 	if ((tmp == NULL && filename[0] != '/') && imdg->dg->doc->uri) {
387 		/* a relative path. create the absolute path. */
388 		GFile *parent = g_file_get_parent(imdg->dg->doc->uri);
389 		gchar *tmp;
390 		/* filename is an URI, not a file path. the function g_file_resolve_relative_path
391 		   does not handle URI parts like %20 (a space) */
392 		tmp = g_uri_unescape_string(filename, NULL);
393 		DEBUG_MSG("unescaped filename=%s\n", tmp);
394 		fullfilename = g_file_resolve_relative_path(parent, tmp);
395 		g_free(tmp);
396 		g_object_unref(parent);
397 	} else if (tmp != NULL || filename[0] == '/') {
398 		fullfilename = g_file_new_for_uri(filename);
399 	} else {
400 		return;
401 	}
402 
403 	if (fullfilename && g_file_query_exists(fullfilename, NULL)) {
404 		gchar *name, *msg;
405 		gchar *path = g_file_get_basename(fullfilename);
406 		DEBUG_MSG("path for fullfilename=%s\n", path);
407 		imdg->pbloader = pbloader_from_filename(path);
408 		g_free(path);
409 
410 #ifdef DEBUG
411 		gchar *path2 = g_file_get_path(fullfilename);
412 		DEBUG_MSG("image_filename_changed: fullfilename=%s, loading!\n", path2);
413 		g_free(path2);
414 #endif
415 		imdg->of = file_openfile_uri_async(fullfilename, NULL, image_loaded_lcb, imdg);
416 		imdg->full_uri = fullfilename;
417 		name = g_file_get_uri(fullfilename);
418 		msg = g_strdup_printf(_("Loading file %s..."), name);
419 		if (imdg->message) {
420 			gtk_widget_destroy(imdg->message);
421 		}
422 		imdg->message = gtk_label_new(msg);
423 		gtk_container_add(GTK_CONTAINER(imdg->frame), imdg->message);
424 		gtk_widget_show(imdg->message);
425 		g_free(msg);
426 		g_free(name);
427 	}
428 }
429 
430 static void
image_adjust_changed(GtkAdjustment * adj,Timage_diag * imdg)431 image_adjust_changed(GtkAdjustment * adj, Timage_diag * imdg)
432 {
433 	GdkPixbuf *tmp_pb;
434 	gint tn_width, tn_height;
435 	DEBUG_MSG("image_adjust_changed started. GTK_IS_WIDGET(imdg->im) == %d\n", GTK_IS_WIDGET(imdg->im));
436 	if (!imdg->pb) {
437 		image_filename_changed(NULL, imdg);
438 		return;
439 	}
440 
441 	tn_width = gtk_adjustment_get_value(imdg->adjustment) * gdk_pixbuf_get_width(imdg->pb);
442 	tn_height = gtk_adjustment_get_value(imdg->adjustment) * gdk_pixbuf_get_height(imdg->pb);
443 	DEBUG_MSG("image_adjust_changed, width=%d, height=%d\n",tn_width,tn_height);
444 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(imdg->dg->spin[0]), tn_width);
445 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(imdg->dg->spin[1]), tn_height);
446 
447 	/* TODO: move this to an idle callback so we will not block the UI */
448 	tmp_pb =
449 		gdk_pixbuf_scale_simple(imdg->pb, tn_width, tn_height,
450 								main_v->globses.image_thumbnail_refresh_quality ? GDK_INTERP_BILINEAR :
451 								GDK_INTERP_NEAREST);
452 
453 	if (GTK_IS_WIDGET(imdg->im)) {
454 		DEBUG_MSG("imdg->im == %p\n", imdg->im);
455 		DEBUG_MSG("gtk_widget_destroy() %p\n", imdg->im);
456 		gtk_widget_destroy(imdg->im);
457 	}
458 
459 	imdg->im = gtk_image_new_from_pixbuf(tmp_pb);
460 	g_object_unref(tmp_pb);
461 	gtk_container_add(GTK_CONTAINER(imdg->frame), imdg->im);
462 	gtk_widget_show(imdg->im);
463 	DEBUG_MSG("image_adjust_changed finished. GTK_IS_WIDGET(imdg->im) == %d\n", GTK_IS_WIDGET(imdg->im));
464 }
465 
466 void
image_insert_dialog_backend(gchar * filename,Tbfwin * bfwin,Ttagpopup * data)467 image_insert_dialog_backend(gchar * filename, Tbfwin * bfwin, Ttagpopup * data)
468 {
469 	static gchar *tagitems[] =
470 		{ "width", "height", "alt", "border", "src", "hspace", "vspace", "align", "name", "usemap", NULL };
471 	gchar *tagvalues[11];
472 	gchar *custom = NULL, *tmp;
473 	Timage_diag *imdg;
474 	GList *popuplist = NULL;
475 	GtkWidget *dgtable, *scale;
476 
477 	imdg = g_new0(Timage_diag, 1);
478 
479 	tmp = main_v->props.image_thumbnailtype; /* it is often uppercase, we need it lowercase */
480 	main_v->props.image_thumbnailtype = g_ascii_strdown(tmp, -1);
481 	g_free(tmp);
482 
483 	imdg->dg = html_diag_new(bfwin, _("Insert thumbnail"));
484 
485 	fill_dialogvalues(tagitems, tagvalues, &custom, (Ttagpopup *) data, imdg->dg);
486 
487 	imdg->frame = gtk_frame_new(_("Preview"));
488 	imdg->message = NULL;
489 
490 	gtk_box_pack_start(GTK_BOX(imdg->dg->vbox), imdg->frame, TRUE, TRUE, 0);
491 
492 	imdg->adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.5, 0.0001, 1.1, 0.001, 0.1, 0.1));
493 #if GTK_CHECK_VERSION(3,0,0)
494 	scale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, imdg->adjustment);
495 #else
496 	scale = gtk_hscale_new(imdg->adjustment);
497 #endif
498 	imdg->adj_changed_id =
499 		g_signal_connect(G_OBJECT(imdg->adjustment), "value_changed", G_CALLBACK(image_adjust_changed), imdg);
500 	gtk_scale_set_digits(GTK_SCALE(scale), 3);
501 	gtk_box_pack_start(GTK_BOX(imdg->dg->vbox), scale, FALSE, FALSE, 0);
502 
503 	dgtable = html_diag_table_in_vbox(imdg->dg, 5, 9);
504 
505 	if (filename) {
506 		imdg->dg->entry[0] = dialog_entry_in_table(filename, dgtable, 1, 7, 0, 1);
507 	} else {
508 		imdg->dg->entry[0] = dialog_entry_in_table(tagvalues[4], dgtable, 1, 7, 0, 1);
509 	}
510 	dialog_mnemonic_label_in_table(_("_Image location:"), imdg->dg->entry[0], dgtable, 0, 1, 0, 1);
511 
512 	gtk_table_attach_defaults(GTK_TABLE(dgtable), file_but_new(imdg->dg->entry[0], 0, bfwin), 7, 9, 0, 1);
513 	g_signal_connect(G_OBJECT(imdg->dg->entry[0]), "changed", G_CALLBACK(image_filename_changed), imdg);
514 
515 	imdg->dg->spin[0] = spinbut_with_value(NULL, 0, 5000, 1.0, 10.0);
516 	imdg->dg->check[0] = gtk_check_button_new_with_label("%");
517 	parse_integer_for_dialog(tagvalues[0], imdg->dg->spin[0], NULL, imdg->dg->check[0]);
518 	dialog_mnemonic_label_in_table(_("_Width:"), imdg->dg->spin[0], dgtable, 6, 7, 1, 2);
519 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->spin[0], 7, 8, 1, 2);
520 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->check[0], 8, 9, 1, 2);
521 
522 	imdg->dg->spin[1] = spinbut_with_value(NULL, 0, 5000, 1.0, 10.0);
523 	imdg->dg->check[1] = gtk_check_button_new_with_label("%");
524 	parse_integer_for_dialog(tagvalues[1], imdg->dg->spin[1], NULL, imdg->dg->check[1]);
525 	dialog_mnemonic_label_in_table(_("Hei_ght:"), imdg->dg->spin[1], dgtable, 6, 7, 2, 3);
526 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->spin[1], 7, 8, 2, 3);
527 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->check[1], 8, 9, 2, 3);
528 
529 	imdg->dg->entry[3] = dialog_entry_in_table(tagvalues[9], dgtable, 1, 3, 1, 2);
530 	dialog_mnemonic_label_in_table(_("_Usemap:"), imdg->dg->entry[3], dgtable, 0, 1, 1, 2);
531 
532 	imdg->dg->entry[1] = dialog_entry_in_table(tagvalues[8], dgtable, 1, 3, 2, 3);
533 	dialog_mnemonic_label_in_table(_("_Name:"), imdg->dg->entry[1], dgtable, 0, 1, 2, 3);
534 
535 	imdg->dg->entry[2] = dialog_entry_in_table(tagvalues[2], dgtable, 1, 6, 3, 4);
536 	dialog_mnemonic_label_in_table(_("Alternate _text:"), imdg->dg->entry[2], dgtable, 0, 1, 3, 4);
537 
538 	imdg->dg->entry[4] = dialog_entry_in_table(custom, dgtable, 1, 6, 4, 5);
539 	dialog_mnemonic_label_in_table(_("Custo_m:"), imdg->dg->entry[4], dgtable, 0, 1, 4, 5);
540 
541 	imdg->dg->spin[3] = spinbut_with_value(tagvalues[5], 0, 500, 1.0, 5.0);
542 	dialog_mnemonic_label_in_table(_("_Hspace:"), imdg->dg->spin[3], dgtable, 6, 7, 3, 4);
543 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->spin[3], 7, 9, 3, 4);
544 
545 	imdg->dg->spin[4] = spinbut_with_value(tagvalues[6], 0, 500, 1.0, 5.0);
546 	dialog_mnemonic_label_in_table(_("_Vspace:"), imdg->dg->spin[4], dgtable, 6, 7, 4, 5);
547 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->spin[4], 7, 9, 4, 5);
548 
549 	popuplist = g_list_append(NULL, "bottom");
550 	popuplist = g_list_append(popuplist, "middle");
551 	popuplist = g_list_append(popuplist, "top");
552 	popuplist = g_list_append(popuplist, "left");
553 	popuplist = g_list_append(popuplist, "right");
554 	imdg->dg->combo[0] = html_diag_combobox_with_popdown_sized(tagvalues[7], popuplist, 1, 90);
555 	g_list_free(popuplist);
556 	dialog_mnemonic_label_in_table(_("_Align:"), imdg->dg->combo[0], dgtable, 3, 4, 1, 2);
557 	gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->combo[0], 4, 6, 1, 2);
558 	if (!get_curlang_option_value(imdg->dg->bfwin, lang_is_XHTML)) {
559 		imdg->dg->spin[2] = spinbut_with_value(tagvalues[3], 0, 500, 1.0, 5.0);
560 		dialog_mnemonic_label_in_table(_("Borde_r:"), imdg->dg->spin[2], dgtable, 3, 4, 2, 3);
561 		gtk_table_attach_defaults(GTK_TABLE(dgtable), imdg->dg->spin[2], 4, 6, 2, 3);
562 	}
563 	if (filename || tagvalues[4]) {
564 		g_signal_emit_by_name(G_OBJECT(imdg->dg->entry[0]), "changed");
565 	}
566 
567 	image_diag_finish(imdg, G_CALLBACK(image_insert_dialogok_lcb));
568 
569 	if (custom)
570 		g_free(custom);
571 }
572 
573 void
thumbnail_insert_dialog(Tbfwin * bfwin)574 thumbnail_insert_dialog(Tbfwin * bfwin)
575 {
576 	image_insert_dialog_backend(NULL, bfwin, NULL);
577 }
578 
579 /*
580 to get the multi thumbnail dialog to work with asynchronous backend is a little complex:
581 
582 1) start loading images asynchronous, to avoid heavy memory usage, we'll limit the number
583 of simultaneous image loads to 3 or so
584 2) each image that is loaded, we check is there are images not loading yet, and we'll fire
585 the next load, we also immediately create the thumbnail and dispose the original pixbuf to
586 free that memory. we will write the HTML string only if all previous files have written
587 their html string in the document, if previous files have not finished we do nothing. If
588 we can write the html string, we also check if the next image is already finished, and
589 should write its html string. If there is no next image, we can call for cleanup
590 */
591 
592 typedef struct {
593 	GtkWidget *win;
594 	GtkWidget *radio[4];
595 	GtkWidget *spinlabels[2];
596 	GtkWidget *spins[2];
597 	GtkTextBuffer *tbuf;
598 	gint mode;
599 	GList *images;
600 	Tbfwin *bfwin;
601 	Tdocument *document;
602 } Tmuthudia;
603 
604 typedef struct {
605 	GFile *imagename;
606 	GFile *thumbname;
607 	Topenfile *of;				/* if != NULL, the image is loading */
608 	gpointer sf;				/* if != NULL, the thumbnail is saving */
609 	gboolean created;			/* both loading and saving is finished */
610 	gchar *string;				/* the string to insert, if NULL && ->create = TRUE means
611 								   that the string is written to the document */
612 	Tmuthudia *mtd;
613 } Timage2thumb;
614 
615 static void
mt_dialog_destroy(GtkWidget * wid,Tmuthudia * mtd)616 mt_dialog_destroy(GtkWidget * wid, Tmuthudia * mtd)
617 {
618 	/* check if we have some images still loading, all images that have 'created == TRUE'
619 	   are ready */
620 	GList *tmplist;
621 	for (tmplist = g_list_first(mtd->images); tmplist; tmplist = g_list_next(tmplist)) {
622 		Timage2thumb *tmp = tmplist->data;
623 		if (tmp->created == FALSE) {
624 			return;
625 		}
626 	}
627 	for (tmplist = g_list_first(mtd->images); tmplist; tmplist = g_list_next(tmplist)) {
628 		Timage2thumb *tmp = tmplist->data;
629 		g_object_unref(tmp->imagename);
630 		g_object_unref(tmp->thumbname);
631 		g_free(tmp);
632 	}
633 	DEBUG_MSG("multi_thumbnail_dialog_destroy, called for mtd=%p\n", mtd);
634 	window_destroy(mtd->win);
635 	g_free(mtd);
636 }
637 
638 /* needs both pixbufs to get the width !! */
639 static void
mt_fill_string(Timage2thumb * i2t,GdkPixbuf * image,GdkPixbuf * thumb)640 mt_fill_string(Timage2thumb * i2t, GdkPixbuf * image, GdkPixbuf * thumb)
641 {
642 	gint tw, th, ow, oh;
643 	gchar *relthumb, *tmp, *relimage;
644 	gchar *doc_curi = NULL;
645 
646 	relimage = tmp = g_file_get_uri(i2t->imagename);
647 
648 	if (i2t->mtd->document->uri) {
649 		doc_curi = g_file_get_uri(i2t->mtd->document->uri);
650 	}
651 
652 	if (i2t->mtd->document->uri) {
653 		relimage = create_relative_link_to(doc_curi, tmp);
654 		g_free(tmp);
655 	}
656 
657 	relthumb = tmp = g_file_get_uri(i2t->thumbname);
658 
659 	if (i2t->mtd->bfwin->current_document->uri) {
660 		relthumb = create_relative_link_to(doc_curi, tmp);
661 		g_free(tmp);
662 	}
663 	if (doc_curi)
664 		g_free(doc_curi);
665 
666 	ow = gdk_pixbuf_get_width(image);
667 	oh = gdk_pixbuf_get_height(image);
668 	tw = gdk_pixbuf_get_width(thumb);
669 	th = gdk_pixbuf_get_height(thumb);
670 	{
671 		Tconvert_table *table, *tmpt;
672 
673 		table = tmpt = g_new(Tconvert_table, 8);
674 		tmpt->my_int = 'r';
675 		tmpt->my_char = g_strdup(relimage);
676 		tmpt++;
677 		tmpt->my_int = 't';
678 		tmpt->my_char = g_strdup(relthumb);
679 		tmpt++;
680 		tmpt->my_int = 'w';
681 		tmpt->my_char = g_strdup_printf("%d", ow);
682 		tmpt++;
683 		tmpt->my_int = 'h';
684 		tmpt->my_char = g_strdup_printf("%d", oh);
685 		tmpt++;
686 		tmpt->my_int = 'x';
687 		tmpt->my_char = g_strdup_printf("%d", tw);
688 		tmpt++;
689 		tmpt->my_int = 'y';
690 		tmpt->my_char = g_strdup_printf("%d", th);
691 		tmpt++;
692 		tmpt->my_int = 'b';
693 		tmpt->my_char = g_strdup("xxx");
694 		tmpt++;
695 		tmpt->my_char = NULL;
696 		i2t->string = replace_string_printflike(main_v->globses.image_thumnailformatstring, table);
697 		DEBUG_MSG("string to insert: %s\n", i2t->string);
698 		tmpt = table;
699 		while (tmpt->my_char) {
700 			g_free(tmpt->my_char);
701 			tmpt++;
702 		}
703 		g_free(table);
704 	}
705 	g_free(relimage);
706 	g_free(relthumb);
707 }
708 
709 static Timage2thumb *
mt_next(Timage2thumb * i2t)710 mt_next(Timage2thumb * i2t)
711 {
712 	GList *tmplist;
713 	tmplist = g_list_find(i2t->mtd->images, i2t);
714 	tmplist = g_list_next(tmplist);
715 	return (tmplist) ? tmplist->data : NULL;
716 }
717 
718 static Timage2thumb *
mt_prev(Timage2thumb * i2t)719 mt_prev(Timage2thumb * i2t)
720 {
721 	GList *tmplist;
722 	tmplist = g_list_find(i2t->mtd->images, i2t);
723 	tmplist = g_list_previous(tmplist);
724 	return (tmplist) ? tmplist->data : NULL;
725 }
726 
727 /* TRUE if already inserted or successfully inserted, FALSE if not yet ready */
728 static gboolean
mt_print_string(Timage2thumb * i2t)729 mt_print_string(Timage2thumb * i2t)
730 {
731 	if (i2t->string == NULL && i2t->created == TRUE) {
732 		/* already added the HTML string */
733 		return TRUE;
734 	} else if (i2t->string) {
735 		Timage2thumb *tmp;
736 		/* check the previous entry */
737 		tmp = mt_prev(i2t);
738 		if (tmp && !mt_print_string(tmp)) {
739 			return FALSE;
740 		}
741 		/* we have a string, but it is not yet added, insert the string */
742 		doc_insert_two_strings(i2t->mtd->document, i2t->string, NULL);
743 		g_free(i2t->string);
744 		i2t->string = NULL;
745 		i2t->created = TRUE;
746 		/* now check if the next string is also already ready for printing */
747 		tmp = mt_next(i2t);
748 		if (tmp)
749 			mt_print_string(tmp);
750 		return TRUE;
751 	} else {
752 		return FALSE;
753 	}
754 }
755 
756 static void mt_start_load(Timage2thumb * i2t);
757 
758 static gboolean
mt_start_next_load(Timage2thumb * i2t)759 mt_start_next_load(Timage2thumb * i2t)
760 {
761 	GList *tmplist;
762 	for (tmplist = g_list_first(i2t->mtd->images); tmplist; tmplist = g_list_next(tmplist)) {
763 		Timage2thumb *tmp = tmplist->data;
764 		if (tmp->of == NULL && tmp->string == NULL && tmp->created == FALSE) {
765 			mt_start_load(tmp);
766 			return TRUE;
767 		}
768 	}
769 	return FALSE;
770 }
771 
772 static void
mt_openfile_lcb(Topenfile_status status,GError * gerror,Trefcpointer * refp,goffset buflen,gpointer callback_data)773 mt_openfile_lcb(Topenfile_status status, GError * gerror, Trefcpointer *refp, goffset buflen,
774 				gpointer callback_data)
775 {
776 	Timage2thumb *i2t = callback_data;
777 	switch (status) {
778 	case OPENFILE_ERROR:
779 	case OPENFILE_ERROR_NOCHANNEL:
780 	case OPENFILE_ERROR_NOREAD:
781 	case OPENFILE_ERROR_CANCELLED:{
782 			/* TODO: should we warn the user ?? */
783 #ifdef DEBUG
784 			{
785 				gchar *path = g_file_get_path(i2t->imagename);
786 				DEBUG_MSG("mt_openfile_lcb, some error! status=%d for image %s\n", status, path);
787 				g_free(path);
788 			}
789 #endif
790 		}
791 		break;
792 	case OPENFILE_CHANNEL_OPENED:
793 		/* do nothing */
794 		break;
795 	case OPENFILE_FINISHED:{
796 			GError *error = NULL;
797 			gboolean nextload;
798 			GdkPixbufLoader *pbloader;
799 #ifdef DEBUG
800 			{
801 				gchar *path = g_file_get_path(i2t->imagename);
802 				DEBUG_MSG("mt_openfile_lcb, finished loading image %s\n", path);
803 				g_free(path);
804 			}
805 #endif
806 			nextload = mt_start_next_load(i2t);	/* fire up the next image load */
807 
808 			gchar *path = g_file_get_path(i2t->imagename);
809 			pbloader = pbloader_from_filename(path);
810 			g_free(path);
811 
812 			if (gdk_pixbuf_loader_write(pbloader, (const guchar *) refp->data, buflen, &error)
813 				&& gdk_pixbuf_loader_close(pbloader, &error)) {
814 				gint tw, th, ow, oh;
815 				GdkPixbuf *image;
816 				GdkPixbuf *thumb;
817 				gsize buflen;
818 				image = gdk_pixbuf_loader_get_pixbuf(pbloader);
819 				if (image) {
820 					ow = gdk_pixbuf_get_width(image);
821 					oh = gdk_pixbuf_get_height(image);
822 					switch (main_v->globses.image_thumbnailsizing_type) {
823 					case 0:
824 						tw = (1.0 * ow / 100 * main_v->globses.image_thumbnailsizing_val1);
825 						th = (1.0 * oh / 100 * main_v->globses.image_thumbnailsizing_val1);
826 						break;
827 					case 1:
828 						tw = main_v->globses.image_thumbnailsizing_val1;
829 						th = (1.0 * tw / ow * oh);
830 						break;
831 					case 2:
832 						th = main_v->globses.image_thumbnailsizing_val1;
833 						tw = (1.0 * th / oh * ow);
834 						break;
835 					default:	/* all fall back to type 3 */
836 						tw = main_v->globses.image_thumbnailsizing_val1;
837 						th = main_v->globses.image_thumbnailsizing_val2;
838 						break;
839 					}
840 #ifdef DEBUG
841 					{
842 						gchar *path = g_file_get_path(i2t->imagename);
843 						DEBUG_MSG("mt_openfile_lcb, start scaling %s to %dx%d\n", path, tw, th);
844 						g_free(path);
845 					}
846 #endif
847 					thumb = gdk_pixbuf_scale_simple(image, tw, th, GDK_INTERP_BILINEAR);
848 #ifdef DEBUG
849 					gchar *path = g_file_get_path(i2t->imagename);
850 					DEBUG_MSG("mt_openfile_lcb, done scaling %s\n", path);
851 					g_free(path);
852 #endif
853 					mt_fill_string(i2t, image, thumb);	/* create the string */
854 					mt_print_string(i2t);	/* print the string and all previous string (if possible) */
855 					g_object_unref(pbloader);
856 					/*gdk_pixbuf_unref(image); will be unreffed with the loader! */
857 					/* save the thumbnail */
858 					if (strcmp(main_v->props.image_thumbnailtype, "jpeg") == 0) {
859 						gdk_pixbuf_save_to_buffer(thumb, (gchar **)&refp->data, &buflen, main_v->props.image_thumbnailtype,
860 												  &error, "quality", "50", NULL);
861 					} else {
862 						gdk_pixbuf_save_to_buffer(thumb, (gchar **)&refp->data, &buflen, main_v->props.image_thumbnailtype,
863 												  &error, NULL);
864 					}
865 					g_object_unref(thumb);
866 					if (error) {
867 						g_print("ERROR while saving thumbnail to buffer: %s\n", error->message);
868 						g_error_free(error);
869 					} else {
870 						GError *error = NULL;
871 						GFileInfo *finfo;
872 						refcpointer_ref(refp);
873 						finfo = g_file_query_info(i2t->thumbname,
874 												  BF_FILEINFO, G_FILE_QUERY_INFO_NONE, NULL, &error);
875 						if (error != NULL) {
876 							g_print("mt_openfile_lcb %s\n ", error->message);
877 							g_error_free(error);
878 						}
879 #ifdef DEBUG
880 						gchar *path = g_file_get_path(i2t->thumbname);
881 						DEBUG_MSG("mt_openfile_lcb, starting thumbnail save to %s\n", path);
882 						g_free(path);
883 #endif
884 						i2t->sf =
885 							file_checkNsave_uri_async(i2t->thumbname, finfo, refp, buflen, FALSE, FALSE,
886 													  async_thumbsave_lcb, NULL, i2t->mtd->bfwin);
887 						refcpointer_unref(refp);
888 					}
889 				} else {
890 					/* ok, this image is not valid, how do we continue ?? */
891 #ifdef DEBUG
892 					gchar *path = g_file_get_path(i2t->imagename);
893 					DEBUG_MSG("mt_openfile_lcb, failed to convert %s to image\n", path);
894 					g_free(path);
895 #endif
896 					i2t->string = g_strdup("");
897 					mt_print_string(i2t);
898 				}
899 				if (!nextload) {
900 					/* there were no more images to load, perhaps we could already call cleanup */
901 					mt_dialog_destroy(NULL, i2t->mtd);
902 				}
903 			}
904 		}
905 		break;
906 	}
907 	/* BUG: the last image that reaches this function should free 'mtd' after it is finished */
908 }
909 
910 static void
mt_start_load(Timage2thumb * i2t)911 mt_start_load(Timage2thumb * i2t)
912 {
913 #ifdef DEBUG
914 	gchar *path = g_file_get_path(i2t->imagename);
915 	DEBUG_MSG("mt_start_load, starting load for %s\n", path);
916 	g_free(path);
917 #endif
918 	i2t->of = file_openfile_uri_async(i2t->imagename, NULL, mt_openfile_lcb, i2t);
919 }
920 
921 static Timage2thumb *
mt_image2thumbnail(Tmuthudia * mtd,gchar * curi)922 mt_image2thumbnail(Tmuthudia * mtd, gchar * curi)
923 {
924 	Timage2thumb *i2t;
925 	gchar *tmp;
926 	GFile *uri;
927 
928 	DEBUG_MSG("mt_image2thumbnail, called for %s\n", curi);
929 	if (!curi)
930 		return NULL;
931 	uri = g_file_new_for_uri(curi);
932 
933 	if (!uri)
934 		return NULL;
935 	i2t = g_new0(Timage2thumb, 1);
936 	i2t->mtd = mtd;
937 	i2t->imagename = uri;
938 	tmp = create_thumbnail_filename(curi);
939 	i2t->thumbname = g_file_new_for_uri(tmp);
940 	g_free(tmp);
941 	return i2t;
942 }
943 
944 static void
multi_thumbnail_ok_clicked(GtkWidget * widget,Tmuthudia * mtd)945 multi_thumbnail_ok_clicked(GtkWidget * widget, Tmuthudia * mtd)
946 {
947 	GSList *files = NULL, *tmplist;
948 	GtkWidget *dialog;
949 	gint i;
950 
951 	gtk_widget_hide(mtd->win);
952 
953 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[0]))) {
954 		main_v->globses.image_thumbnailsizing_type = 0;
955 	} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[1]))) {
956 		main_v->globses.image_thumbnailsizing_type = 1;
957 	} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[2]))) {
958 		main_v->globses.image_thumbnailsizing_type = 2;
959 	} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[3]))) {
960 		main_v->globses.image_thumbnailsizing_type = 3;
961 	}
962 	{
963 		gchar *tmp;
964 		GtkTextIter start, end;
965 		gtk_text_buffer_get_bounds(mtd->tbuf, &start, &end);
966 		tmp = gtk_text_buffer_get_text(mtd->tbuf, &start, &end, FALSE);
967 		if (tmp) {
968 			if (main_v->globses.image_thumnailformatstring)
969 				g_free(main_v->globses.image_thumnailformatstring);
970 			main_v->globses.image_thumnailformatstring = tmp;
971 		}
972 	}
973 
974 	dialog =
975 		file_chooser_dialog(mtd->bfwin, _("Select files for thumbnail creation"),
976 							GTK_FILE_CHOOSER_ACTION_OPEN, NULL, FALSE, TRUE, "webimage", FALSE);
977 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
978 		files = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(dialog));
979 	}
980 	gtk_widget_destroy(dialog);
981 
982 	main_v->globses.image_thumbnailsizing_val1 =
983 		gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mtd->spins[0]));
984 	main_v->globses.image_thumbnailsizing_val2 =
985 		gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mtd->spins[1]));
986 
987 	i = 3;
988 	tmplist = files;
989 	while (tmplist) {
990 		Timage2thumb *i2t;
991 		i2t = mt_image2thumbnail(mtd, (gchar *) tmplist->data);
992 		mtd->images = g_list_prepend(mtd->images, i2t);
993 		if (i > 0) {
994 			mt_start_load(i2t);
995 			i--;
996 		}
997 		tmplist = g_slist_next(tmplist);
998 	}
999 	mtd->images = g_list_reverse(mtd->images);
1000 	/* BUG: should we free the list of files now ?? */
1001 }
1002 
1003 static void
multi_thumbnail_cancel_clicked(GtkWidget * widget,Tmuthudia * mtd)1004 multi_thumbnail_cancel_clicked(GtkWidget * widget, Tmuthudia * mtd)
1005 {
1006 	mt_dialog_destroy(NULL, mtd);
1007 }
1008 
1009 static void
multi_thumbnail_radio_toggled_lcb(GtkToggleButton * togglebutton,Tmuthudia * mtd)1010 multi_thumbnail_radio_toggled_lcb(GtkToggleButton * togglebutton, Tmuthudia * mtd)
1011 {
1012 	/* only call this for activate, not for de-activate */
1013 	if (gtk_toggle_button_get_active(togglebutton)) {
1014 		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[0]))) {
1015 			gtk_widget_hide(mtd->spins[1]);
1016 			gtk_widget_hide(mtd->spinlabels[1]);
1017 			gtk_label_set_text(GTK_LABEL(mtd->spinlabels[0]), _("Scaling (%)"));
1018 		} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[1]))) {
1019 			gtk_widget_hide(mtd->spins[1]);
1020 			gtk_widget_hide(mtd->spinlabels[1]);
1021 			gtk_label_set_text(GTK_LABEL(mtd->spinlabels[0]), _("Width"));
1022 		} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mtd->radio[2]))) {
1023 			gtk_widget_hide(mtd->spins[1]);
1024 			gtk_widget_hide(mtd->spinlabels[1]);
1025 			gtk_label_set_text(GTK_LABEL(mtd->spinlabels[0]), _("Height"));
1026 		} else {
1027 			gtk_widget_show(mtd->spins[1]);
1028 			gtk_widget_show(mtd->spinlabels[1]);
1029 			gtk_label_set_text(GTK_LABEL(mtd->spinlabels[0]), _("Width"));
1030 		}
1031 	}
1032 }
1033 
1034 void
multi_thumbnail_dialog(Tbfwin * bfwin)1035 multi_thumbnail_dialog(Tbfwin * bfwin)
1036 {
1037 	Tmuthudia *mtd;
1038 	GtkWidget *vbox, *hbox, *but, *table, *label, *scrolwin, *textview;
1039 	gint tb;
1040 	gchar *tmp;
1041 
1042 	if (!bfwin->current_document) {
1043 		return;
1044 	}
1045 
1046 	tmp = main_v->props.image_thumbnailtype; /* it is often uppercase, we need it lowercase */
1047 	main_v->props.image_thumbnailtype = g_ascii_strdown(tmp, -1);
1048 	g_free(tmp);
1049 
1050 	mtd = g_new0(Tmuthudia, 1);
1051 	mtd->bfwin = bfwin;
1052 	mtd->document = bfwin->current_document;
1053 	mtd->win =
1054 		window_full2(_("Multi thumbnail"), GTK_WIN_POS_CENTER, 5, G_CALLBACK(mt_dialog_destroy), mtd, TRUE,
1055 					 bfwin->main_window);
1056 	vbox = gtk_vbox_new(FALSE, 5);
1057 	gtk_container_add(GTK_CONTAINER(mtd->win), vbox);
1058 
1059 	table = gtk_table_new(4, 3, FALSE);
1060 	mtd->radio[0] = gtk_radio_button_new_with_label(NULL, _("By scaling"));
1061 	mtd->radio[1] =
1062 		gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(mtd->radio[0]),
1063 													_("By width, keep aspect ratio"));
1064 	mtd->radio[2] =
1065 		gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(mtd->radio[0]),
1066 													_("By height, keep aspect ratio"));
1067 	mtd->radio[3] =
1068 		gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(mtd->radio[0]),
1069 													_("By width and height, ignore aspect ratio"));
1070 	mtd->spinlabels[0] = gtk_label_new("");
1071 	mtd->spinlabels[1] = gtk_label_new(_("Height"));
1072 	mtd->spins[0] = gtk_spin_button_new_with_range(0, 1000, 1);
1073 	mtd->spins[1] = gtk_spin_button_new_with_range(0, 1000, 1);
1074 
1075 	g_signal_connect(G_OBJECT(mtd->radio[0]), "toggled", G_CALLBACK(multi_thumbnail_radio_toggled_lcb), mtd);
1076 	g_signal_connect(G_OBJECT(mtd->radio[1]), "toggled", G_CALLBACK(multi_thumbnail_radio_toggled_lcb), mtd);
1077 	g_signal_connect(G_OBJECT(mtd->radio[2]), "toggled", G_CALLBACK(multi_thumbnail_radio_toggled_lcb), mtd);
1078 	g_signal_connect(G_OBJECT(mtd->radio[3]), "toggled", G_CALLBACK(multi_thumbnail_radio_toggled_lcb), mtd);
1079 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(mtd->spins[0]), main_v->globses.image_thumbnailsizing_val1);
1080 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(mtd->spins[1]), main_v->globses.image_thumbnailsizing_val2);
1081 
1082 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->radio[0], 0, 1, 0, 1);
1083 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->radio[1], 0, 1, 1, 2);
1084 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->radio[2], 0, 1, 2, 3);
1085 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->radio[3], 0, 1, 3, 4);
1086 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->spinlabels[0], 1, 2, 0, 1);
1087 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->spinlabels[1], 1, 2, 1, 2);
1088 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->spins[0], 2, 3, 0, 1);
1089 	gtk_table_attach_defaults(GTK_TABLE(table), mtd->spins[1], 2, 3, 1, 2);
1090 
1091 	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
1092 
1093 	label =
1094 		gtk_label_new(_
1095 					  ("%r: original filename  %t: thumbnail filename\n%w: original width  %h: original height\n%x: thumbnail width  %y: thumbnail height\n%b: original size (bytes)"));
1096 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1097 
1098 	scrolwin =
1099 		textview_buffer_in_scrolwin(&textview, -1, -1, main_v->globses.image_thumnailformatstring,
1100 									GTK_WRAP_CHAR);
1101 	mtd->tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
1102 	gtk_box_pack_start(GTK_BOX(vbox), scrolwin, TRUE, TRUE, 0);
1103 #if GTK_CHECK_VERSION(3,0,0)
1104 	hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
1105 #else
1106 	hbox = gtk_hbutton_box_new();
1107 #endif
1108 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
1109 	gtk_box_set_spacing(GTK_BOX(hbox), 1);
1110 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1111 	but = bf_stock_cancel_button(G_CALLBACK(multi_thumbnail_cancel_clicked), mtd);
1112 	gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, FALSE, 5);
1113 	but = bf_stock_ok_button(G_CALLBACK(multi_thumbnail_ok_clicked), mtd);
1114 	gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, FALSE, 5);
1115 	gtk_window_set_default(GTK_WINDOW(mtd->win), but);
1116 	gtk_widget_show_all(mtd->win);
1117 
1118 	tb = main_v->globses.image_thumbnailsizing_type < 4 ? main_v->globses.image_thumbnailsizing_type : 0;
1119 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mtd->radio[tb]), TRUE);
1120 	multi_thumbnail_radio_toggled_lcb(GTK_TOGGLE_BUTTON(mtd->radio[tb]), mtd);
1121 }
1122