1 /*	mainwindow.c
2 	Copyright (C) 2004-2021 Mark Tyler and Dmitry Groshev
3 
4 	This file is part of mtPaint.
5 
6 	mtPaint 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 	mtPaint 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 mtPaint in the file COPYING.
18 */
19 
20 #include "global.h"
21 #undef _
22 #define _(X) X
23 
24 #include "mygtk.h"
25 #include "memory.h"
26 #include "vcode.h"
27 #include "ani.h"
28 #include "png.h"
29 #include "mainwindow.h"
30 #include "viewer.h"
31 #include "otherwindow.h"
32 #include "inifile.h"
33 #include "canvas.h"
34 #include "polygon.h"
35 #include "layer.h"
36 #include "info.h"
37 #include "prefs.h"
38 #include "channels.h"
39 #include "toolbar.h"
40 #include "csel.h"
41 #include "shifter.h"
42 #include "spawn.h"
43 #include "font.h"
44 #include "icons.h"
45 #include "thread.h"
46 
47 
48 typedef struct {
49 	int idx_c, nidx_c, cnt_c;
50 	int cline_d, settings_d, layers_d;
51 	int impmode;
52 	int *strs_c;
53 	void **drop, **clip;
54 	void **clipboard;
55 	void **dockpage1;
56 	void **keyslot;
57 } main_dd;
58 
59 #define GREY_W 153
60 #define GREY_B 102
61 const unsigned char greyz[2] = {GREY_W, GREY_B}; // For opacity squares
62 
63 char *channames[NUM_CHANNELS + 1], *allchannames[NUM_CHANNELS + 1];
64 char *cspnames[NUM_CSPACES];
65 
66 char *channames_[NUM_CHANNELS + 1] =
67 		{ _("Image"), _("Alpha"), _("Selection"), _("Mask"), NULL };
68 char *cspnames_[NUM_CSPACES] =
69 		{ _("RGB"), _("sRGB"), "LXN" };
70 
71 ///	INIFILE ENTRY LISTS
72 
73 typedef struct {
74 	char *name;
75 	int *var;
76 	int defv;
77 } inilist;
78 
79 static inilist ini_bool[] = {
80 	{ "layermainToggle",	&show_layers_main,	FALSE },
81 	{ "sharperReduce",	&sharper_reduce,	FALSE },
82 	{ "tga565",		&tga_565,		FALSE },
83 	{ "tgaDefdir",		&tga_defdir,		FALSE },
84 	{ "tgaRLE",		&tga_RLE,		FALSE },
85 	{ "lbmPBM",		&lbm_pbm,		FALSE },
86 	{ "disableTransparency", &opaque_view,		FALSE },
87 	{ "smudgeOpacity",	&smudge_mode,		FALSE },
88 	{ "showMenuIcons",	&show_menu_icons,	FALSE },
89 	{ "showTileGrid",	&show_tile_grid,	FALSE },
90 	{ "applyICC",		&apply_icc,		FALSE },
91 	{ "scrollwheelZOOM",	&scroll_zoom,		FALSE },
92 	{ "cursorZoom",		&cursor_zoom,		FALSE },
93 	{ "layerOverlay",	&layer_overlay,		FALSE },
94 	{ "paintGamma",		&paint_gamma,		FALSE },
95 	{ "patternB",		&pattern_B,		FALSE },
96 	{ "arrowScroll",	&arrow_scroll,		FALSE },
97 	{ "tablet_use_size",	tablet_tool_use + 0,	FALSE },
98 	{ "tablet_use_flow",	tablet_tool_use + 1,	FALSE },
99 	{ "tablet_use_opacity",	tablet_tool_use + 2,	FALSE },
100 	{ "pasteCommit",	&paste_commit,		TRUE  },
101 	{ "couple_RGBA",	&RGBA_mode,		TRUE  },
102 	{ "gridToggle",		&mem_show_grid,		TRUE  },
103 	{ "optimizeChequers",	&chequers_optimize,	TRUE  },
104 	{ "quitToggle",		&q_quit,		TRUE  },
105 	{ "continuousPainting",	&mem_continuous,	TRUE  },
106 	{ "opacityToggle",	&mem_undo_opacity,	TRUE  },
107 	{ "imageCentre",	&canvas_image_centre,	TRUE  },
108 	{ "view_focus",		&vw_focus_on,		TRUE  },
109 	{ "pasteToggle",	&show_paste,		TRUE  },
110 	{ "cursorToggle",	&cursor_tool,		TRUE  },
111 	{ "autopreviewToggle",	&brcosa_auto,		TRUE  },
112 	{ "colorGrid",		&color_grid,		TRUE  },
113 	{ "defaultGamma",	&use_gamma,		TRUE  },
114 	{ "undoableLoad",	&undo_load,		TRUE  },
115 	{ "tiffPredictor",	&tiff_predictor,	TRUE  },
116 	{ "lbmPack",		&lbm_pack,		TRUE  },
117 	{ "lbmIgnoreTrans",	&lbm_untrans,		TRUE  },
118 #if STATUS_ITEMS != 5
119 #error Wrong number of "status?Toggle" inifile items defined
120 #endif
121 	{ "status0Toggle",	status_on + 0,		TRUE  },
122 	{ "status1Toggle",	status_on + 1,		TRUE  },
123 	{ "status2Toggle",	status_on + 2,		TRUE  },
124 	{ "status3Toggle",	status_on + 3,		TRUE  },
125 	{ "status4Toggle",	status_on + 4,		TRUE  },
126 #if TOOLBAR_MAX != 6
127 #error Wrong number of "toolbar?" inifile items defined
128 #endif
129 	{ "toolbar1",		toolbar_status + 1,	TRUE  },
130 	{ "toolbar2",		toolbar_status + 2,	TRUE  },
131 	{ "toolbar3",		toolbar_status + 3,	TRUE  },
132 	{ "toolbar4",		toolbar_status + 4,	TRUE  },
133 	{ "toolbar5",		toolbar_status + 5,	TRUE  },
134 	{ "fontAntialias0",	&font_aa,		TRUE  },
135 	{ "fontAntialias1",	&font_bk,		FALSE },
136 	{ "fontAntialias2",	&font_r,		FALSE },
137 #ifdef U_FREETYPE
138 	{ "fontAntialias3",	&font_obl,		FALSE },
139 	{ "ftSetDPI",		&ft_setdpi,		TRUE  },
140 #endif
141 	{ "fontSetDPI",		&font_setdpi,		FALSE },
142 	{ NULL,			NULL }
143 };
144 
145 static inilist ini_int[] = {
146 	{ "jpegQuality",	&jpeg_quality,		85  },
147 	{ "pngCompression",	&png_compression,	9   },
148 	{ "jpeg2000Rate",	&jp2_rate,		1   },
149 	{ "lzmaPreset",		&lzma_preset,		9   },
150 	{ "zstdLevel",		&zstd_level,		9   },
151 	{ "webpPreset",		&webp_preset,		1   },
152 	{ "webpQuality",	&webp_quality,		90  },
153 	{ "webpCompression",	&webp_compression,	9   },
154 	{ "lbmMask",		&lbm_mask,		CHN_MASK },
155 	{ "silence_limit",	&silence_limit,		18  },
156 	{ "gradientOpacity",	&grad_opacity,		128 },
157 	{ "gridMin",		&mem_grid_min,		8   },
158 	{ "undoMBlimit",	&mem_undo_limit,	0   },
159 	{ "undoCommon",		&mem_undo_common,	25  },
160 	{ "maxThreads",		&maxthreads,		0   },
161 	{ "kpixThreads",	&kpix_threads,		256 },
162 	{ "backgroundGrey",	&mem_background,	180 },
163 	{ "pixelNudge",		&mem_nudge,		8   },
164 	{ "recentFiles",	&recent_files,		10  },
165 	{ "lastspalType",	&spal_mode,		2   },
166 	{ "posterizeMode",	&def_bcsp.pmode,	0   },
167 	{ "panSize",		&max_pan,		128 },
168 	{ "undoDepth",		&mem_undo_depth,	DEF_UNDO },
169 	{ "tileWidth",		&tgrid_dx,		32  },
170 	{ "tileHeight",		&tgrid_dy,		32  },
171 	{ "gridRGB",		grid_rgb + GRID_NORMAL,	RGB_2_INT( 50,  50,  50) },
172 	{ "gridBorder",		grid_rgb + GRID_BORDER,	RGB_2_INT(  0, 219,   0) },
173 	{ "gridTrans",		grid_rgb + GRID_TRANS,	RGB_2_INT(  0, 109, 109) },
174 	{ "gridTile",		grid_rgb + GRID_TILE,	RGB_2_INT(170, 170, 170) },
175 	{ "gridSegment",	grid_rgb + GRID_SEGMENT,RGB_2_INT(219, 219,   0) },
176 	{ "palAB",		&mem_pal_ab_c,		RGB_2_INT(53,   53, 162) },
177 	{ "palIndex",		&mem_pal_id_c,		RGB_2_INT(200, 200, 200) },
178 	{ "tablet_value_size",	tablet_tool_factor + 0,	MAX_TF },
179 	{ "tablet_value_flow",	tablet_tool_factor + 1,	MAX_TF },
180 	{ "tablet_value_opacity", tablet_tool_factor + 2,MAX_TF },
181 	{ "fontBackground",	&font_bkg,		0   },
182 	{ "fontAngle",		&font_angle,		0   },
183 	{ "fontAlign",		&font_align,		0   },
184 	{ "fontDPI",		&font_dpi,		72  },
185 	{ "fontSpacing",	&font_spacing,		0   },
186 #ifdef U_FREETYPE
187 	{ "fontSizeBitmap",	&font_bmsize,		1   },
188 	{ "fontSize",		&font_size,		12  },
189 	{ "font_dirs",		&font_dirs,		0   },
190 #endif
191 	{ NULL,			NULL }
192 };
193 
194 
195 void **main_window_, **main_keys, **settings_dock, **layers_dock, **main_split,
196 	**drawing_canvas, **scrolledwindow_canvas,
197 	**menu_slots[TOTAL_MENU_IDS];
198 
199 static void **dock_area, **dock_book, **main_menubar;
200 
201 int	view_image_only, viewer_mode, drag_index, q_quit, cursor_tool;
202 int	show_menu_icons, paste_commit, scroll_zoom, arrow_scroll;
203 int	drag_index_vals[2], cursor_corner, use_gamma, view_vsplit;
204 int	files_passed, cmd_mode, tablet_working;
205 char **file_args, **script_cmds;
206 
207 static int show_dock;
208 static int mouse_left_canvas, is_tracking;
209 static int cvxy[2];	// canvas window position
210 
211 typedef struct {
212 	int mode;	// (tool_type + 1) if drawn, 0 if not
213 	int x, y, s;	// Top left corner and size
214 	int cx, cy;	// Clone perimeter offset
215 } perim_info;
216 
217 static perim_info perim_state;	// Tool perimeter
218 int perim_wx, perim_wy;	// Cursor position
219 
220 #define perim_status	perim_state.mode
221 #define perim_x		perim_state.x
222 #define perim_y		perim_state.y
223 #define perim_s		perim_state.s
224 #define perim_cx	perim_state.cx
225 #define perim_cy	perim_state.cy
226 
227 static void repaint_perim(rgbcontext *ctx);	// Redraw perimeter around mouse cursor
228 static void clear_perim(perim_info *p);		// Clear perimeter around mouse cursor
229 static void move_perim(int x, int y);		// Move perimeter to a new location
230 
clear_perim_real(int x0,int y0,int s)231 static void clear_perim_real(int x0, int y0, int s)
232 {
233 	int x1, y1, zoom = 1, scale = 1;
234 
235 
236 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
237 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
238 	else scale = rint(can_zoom);
239 
240 	x1 = margin_main_x + ((x0 + s - 1) * scale) / zoom + scale - 1;
241 	y1 = margin_main_y + ((y0 + s - 1) * scale) / zoom + scale - 1;
242 	x0 = margin_main_x + (x0 * scale) / zoom;
243 	y0 = margin_main_y + (y0 * scale) / zoom;
244 
245 	repaint_canvas(x0, y0, 1, y1 - y0 + 1);
246 	repaint_canvas(x1, y0, 1, y1 - y0 + 1);
247 	repaint_canvas(x0 + 1, y0, x1 - x0 - 1, 1);
248 	repaint_canvas(x0 + 1, y1, x1 - x0 - 1, 1);
249 }
250 
pressed_load_recent(int item)251 static void pressed_load_recent(int item)
252 {
253 	if ((layers_total ? check_layers_for_changes() :
254 		check_for_changes()) == 1) return;
255 
256 	do_a_load(recent_filenames[item - 1], undo_load); // Load requested file
257 }
258 
pressed_crop()259 static void pressed_crop()
260 {
261 	int res, rect[4];
262 
263 
264 	if ( marq_status != MARQUEE_DONE ) return;
265 	marquee_at(rect);
266 	if ((rect[0] == 0) && (rect[2] >= mem_width) &&
267 		(rect[1] == 0) && (rect[3] >= mem_height)) return;
268 
269 	res = mem_image_resize(rect[2], rect[3], -rect[0], -rect[1], 0);
270 
271 	if (!res)
272 	{
273 		pressed_select(FALSE);
274 		change_to_tool(DEFAULT_TOOL_ICON);
275 		update_stuff(UPD_GEOM);
276 	}
277 	else memory_errors(res);
278 }
279 
280 static multi_ext *script_rect(int *rect);
281 static void select_poly(multi_ext *mx);
282 
pressed_select(int all)283 void pressed_select(int all)
284 {
285 	int i = 0;
286 
287 	/* Remove old selection */
288 	if (marq_status != MARQUEE_NONE)
289 	{
290 		i = UPD_SEL;
291 		if (marq_status >= MARQUEE_PASTE) i = UPD_SEL | CF_DRAW;
292 		else paint_marquee(MARQ_HIDE, 0, 0, NULL);
293 		marq_status = MARQUEE_NONE;
294 	}
295 	if ((tool_type == TOOL_POLYGON) && (poly_status != POLY_NONE))
296 	{
297 		poly_points = 0;
298 		poly_status = POLY_NONE;
299 		i = UPD_SEL | CF_DRAW; // Have to erase polygon
300 	}
301 	/* And deal with selection persistence too */
302 	marq_x1 = marq_y1 = marq_x2 = marq_y2 = -1;
303 
304 	while (all) /* Select entire canvas */
305 	{
306 		multi_ext *mx;
307 		int rxy[4];
308 
309 		if ((mx = script_rect(rxy))) // Script wants a polygon
310 		{
311 			change_to_tool(TTB_POLY);
312 			select_poly(mx);
313 			free(mx);
314 			break;
315 		}
316 		clip(rxy, 0, 0, mem_width - 1, mem_height - 1, rxy);
317 		/* We are selecting an area, so block inside-out selections */
318 		if ((rxy[0] > rxy[2]) || (rxy[1] > rxy[3])) break;
319 		i |= UPD_SEL;
320 		copy4(marq_xy, rxy);
321 		if (tool_type != TOOL_SELECT)
322 		{
323 			/* Switch tool, and let that & marquee persistence
324 			 * do all the rest except full redraw */
325 			change_to_tool(TTB_SELECT);
326 			i &= CF_DRAW;
327 			break;
328 		}
329 		marq_status = MARQUEE_DONE;
330 		if (i & CF_DRAW) break; // Full redraw will draw marquee too
331 		paint_marquee(MARQ_SHOW, 0, 0, NULL);
332 		break;
333 	}
334 	if (i) update_stuff(i);
335 }
336 
pressed_remove_unused()337 static void pressed_remove_unused()
338 {
339 	if (mem_remove_unused_check() <= 0)
340 	{
341 		if (!script_cmds) alert_box(_("Error"),
342 			_("There were no unused colours to remove!"), NULL);
343 	}
344 	else
345 	{
346 		spot_undo(UNDO_XPAL);
347 
348 		mem_remove_unused();
349 		mem_undo_prepare();
350 
351 		update_stuff(UPD_TPAL);
352 	}
353 }
354 
pressed_default_pal()355 static void pressed_default_pal()
356 {
357 	spot_undo(UNDO_PAL);
358 	mem_pal_copy( mem_pal, mem_pal_def );
359 	mem_cols = mem_pal_def_i;
360 	update_stuff(UPD_PAL);
361 }
362 
pressed_remove_duplicates()363 static void pressed_remove_duplicates()
364 {
365 	char *mess;
366 	int dups = scan_duplicates();
367 
368 	if (!dups)
369 	{
370 		if (!script_cmds) alert_box(_("Error"),
371 			_("The palette does not contain 2 colours that have identical RGB values"), NULL);
372 		return;
373 	}
374 	mess = g_strdup_printf(__("The palette contains %i colours that have identical RGB values.  Do you really want to merge them into one index and realign the canvas?"), dups);
375 	if (script_cmds || (alert_box(_("Warning"), mess, _("Yes"), _("No"), NULL) == 1))
376 	{
377 		spot_undo(UNDO_XPAL);
378 
379 		remove_duplicates();
380 		mem_undo_prepare();
381 		update_stuff(UPD_PAL);
382 	}
383 	g_free(mess);
384 }
385 
pressed_dither_A()386 static void pressed_dither_A()
387 {
388 	mem_find_dither(mem_col_A24.red, mem_col_A24.green, mem_col_A24.blue);
389 	update_stuff(UPD_ABP);
390 }
391 
392 // System clipboard import
393 
394 static clipform_dd clip_formats[] = {
395 	{ "application/x-mtpaint-pmm", (void *)(FT_PMM | FTM_EXTEND) },
396 	{ "application/x-mtpaint-clipboard", (void *)(FT_PNG | FTM_EXTEND) },
397 	{ "image/png", (void *)(FT_PNG) },
398 	{ "image/bmp", (void *)(FT_BMP) },
399 	{ "image/x-bmp", (void *)(FT_BMP) },
400 	{ "image/x-MS-bmp", (void *)(FT_BMP) },
401 #ifdef U_TIFF
402 	{ "image/tiff", (void *)(FT_TIFF) },
403 #endif
404 #ifdef HAVE_PIXMAPS
405 	/* These two don't make sense without X */
406 	{ "PIXMAP", (void *)(FT_PIXMAP), sizeof(XID_type), 32 },
407 	{ "BITMAP", (void *)(FT_PIXMAP), sizeof(XID_type), 32 },
408 	/* !!! BITMAP requests are handled same as PIXMAP - because it is only
409 	 * done to appease buggy XPaint which requests both and crashes if
410 	 * receiving only one - WJ */
411 #endif
412 };
413 #define CLIP_TARGETS (sizeof(clip_formats) / sizeof(clip_formats[0]))
414 
415 /* Seems it'll be better to prefer BMP when talking to the likes of GIMP -
416  * they send PNGs really slowly (likely, compressed to the max); but not
417  * everyone supports alpha in BMPs. */
418 
clipboard_import_fn(main_dd * dt,void ** wdata,int what,void ** where,copy_ext * cdata)419 static int clipboard_import_fn(main_dd *dt, void **wdata, int what, void **where,
420 	copy_ext *cdata)
421 {
422 	int form = (int)cdata->format->id;
423 	if ((dt->impmode == FS_PNG_LOAD) && undo_load) form |= FTM_UNDO;
424 	return (load_mem_image(cdata->data, cdata->len, dt->impmode, form) == 1);
425 }
426 
import_clipboard(int mode)427 int import_clipboard(int mode)
428 {
429 	main_dd *dt = GET_DDATA(main_window_);
430 	if (cmd_mode) return (FALSE); // !!! Needs active GTK+ to work
431 	dt->impmode = mode;
432 	return (cmd_checkv(dt->clipboard, CLIP_PROCESS));
433 }
434 
setup_clip_save(ls_settings * settings)435 static void setup_clip_save(ls_settings *settings)
436 {
437 	init_ls_settings(settings, NULL);
438 	memcpy(settings->img, mem_clip.img, sizeof(chanlist));
439 	settings->pal = mem_pal;
440 	settings->width = mem_clip_w;
441 	settings->height = mem_clip_h;
442 	settings->bpp = mem_clip_bpp;
443 	settings->colors = mem_cols;
444 }
445 
clipboard_export_fn(main_dd * dt,void ** wdata,int what,void ** where,copy_ext * cdata)446 static void clipboard_export_fn(main_dd *dt, void **wdata, int what, void **where,
447 	copy_ext *cdata)
448 {
449 	ls_settings settings;
450 	unsigned char *buf, *pp[2];
451 	int res, len, type;
452 
453 	if (!cdata->format) return; // Someone else stole system clipboard
454 	if (!mem_clipboard) return; // Our own clipboard got emptied
455 
456 	/* Prepare settings */
457 	setup_clip_save(&settings);
458 	settings.mode = FS_CLIPBOARD;
459 	settings.ftype = type = (int)cdata->format->id;
460 	settings.png_compression = 1; // Speed is of the essence
461 
462 	res = save_mem_image(&buf, &len, &settings);
463 	if (res) return; // No luck creating in-memory image
464 
465 	pp[1] = (pp[0] = buf) + len;
466 	cmd_setv(where, pp, COPY_DATA);
467 	free(buf);
468 }
469 
export_clipboard()470 static int export_clipboard()
471 {
472 	main_dd *dt = GET_DDATA(main_window_);
473 	if (!mem_clipboard) return (FALSE);
474 	return (cmd_checkv(dt->clipboard, CLIP_OFFER));
475 }
476 
gui_save(char * filename,ls_settings * settings)477 int gui_save(char *filename, ls_settings *settings)
478 {
479 	int res = -2, fflags = file_formats[settings->ftype].flags;
480 	char *mess = NULL, *f8;
481 
482 	/* Mismatched format - raise an error right here */
483 	if ((fflags & FF_NOSAVE) || !(fflags & FF_SAVE_MASK))
484 	{
485 		int maxc = 0;
486 		char *fform = NULL, *fname = file_formats[settings->ftype].name;
487 
488 		/* RGB to indexed (or to unsaveable) */
489 		if (mem_img_bpp == 3) fform = __("RGB");
490 		/* Indexed to RGB, or to unsaveable format */
491 		else if (!(fflags & FF_IDX) || (fflags & FF_NOSAVE))
492 			fform = __("indexed");
493 		/* More than 16 colors */
494 		else if (fflags & FF_16) maxc = 16;
495 		/* More than 2 colors */
496 		else maxc = 2;
497 		/* Build message */
498 		if (fform) mess = g_strdup_printf(__("You are trying to save an %s image to an %s file which is not possible.  I would suggest you save with a PNG extension."),
499 			fform, fname);
500 		else mess = g_strdup_printf(__("You are trying to save an %s file with a palette of more than %d colours.  Either use another format or reduce the palette to %d colours."),
501 			fname, maxc, maxc);
502 	}
503 	else
504 	{
505 		/* Commit paste if required */
506 		if (!script_cmds && paste_commit && (marq_status >= MARQUEE_PASTE))
507 		{
508 			commit_paste(FALSE, NULL);
509 			pen_down = 0;
510 			mem_undo_prepare();
511 			pressed_select(FALSE);
512 		}
513 
514 		/* Prepare to save image */
515 		memcpy(settings->img, mem_img, sizeof(chanlist));
516 		settings->pal = mem_pal;
517 		settings->width = mem_width;
518 		settings->height = mem_height;
519 		settings->bpp = mem_img_bpp;
520 		settings->colors = mem_cols;
521 
522 		res = save_image(filename, settings);
523 	}
524 	if (res < 0)
525 	{
526 		if (res == -1)
527 		{
528 			f8 = gtkuncpy(NULL, filename, 0);
529 			mess = g_strdup_printf(__("Unable to save file: %s"), f8);
530 			g_free(f8);
531 		}
532 		if (mess)
533 		{
534 			alert_box(_("Error"), mess, NULL);
535 			g_free(mess);
536 		}
537 	}
538 	else
539 	{
540 		notify_unchanged(filename);
541 		register_file(filename);
542 	}
543 
544 	return res;
545 }
546 
pressed_save_file()547 static void pressed_save_file()
548 {
549 	ls_settings settings;
550 
551 	while (mem_filename)
552 	{
553 		init_ls_settings(&settings, NULL);
554 		settings.ftype = file_type_by_ext(mem_filename, FF_IMAGE);
555 		if (settings.ftype == FT_NONE) break;
556 		settings.mode = FS_PNG_SAVE;
557 		if (gui_save(mem_filename, &settings) < 0) break;
558 		return;
559 	}
560 	file_selector(FS_PNG_SAVE);
561 }
562 
563 char mem_clip_file[PATHBUF];
564 
load_clip(int item)565 static void load_clip(int item)
566 {
567 	char clip[PATHBUF];
568 	int i;
569 
570 	if (item == -1) // System clipboard
571 		i = import_clipboard(FS_CLIPBOARD);
572 	else // Disk file
573 	{
574 		snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
575 		i = load_image(clip, FS_CLIP_FILE, FT_PNG) == 1;
576 	}
577 
578 	if (!i) alert_box(_("Error"), _("Unable to load clipboard"), NULL);
579 
580 	update_stuff(UPD_XCOPY);
581 	if (i && (MEM_BPP >= mem_clip_bpp)) pressed_paste(TRUE);
582 }
583 
save_clip(int item)584 static void save_clip(int item)
585 {
586 	ls_settings settings;
587 	char clip[PATHBUF];
588 	int i;
589 
590 	if (item == -1) // Exporting clipboard
591 	{
592 		export_clipboard();
593 		return;
594 	}
595 
596 	/* Prepare settings */
597 	setup_clip_save(&settings);
598 	settings.mode = FS_CLIP_FILE;
599 	settings.ftype = FT_PNG;
600 
601 	snprintf(clip, PATHBUF, "%s%i", mem_clip_file, item);
602 	i = save_image(clip, &settings);
603 
604 	if (i) alert_box(_("Error"), _("Unable to save clipboard"), NULL);
605 }
606 
pressed_opacity(int opacity)607 void pressed_opacity(int opacity)
608 {
609 	if (IS_INDEXED) opacity = 255;
610 	tool_opacity = opacity < 1 ? 1 : opacity > 255 ? 255 : opacity;
611 	update_stuff(UPD_OPAC);
612 }
613 
toggle_view()614 static void toggle_view()
615 {
616 	view_image_only = !view_image_only;
617 
618 	cmd_showhide(main_menubar, !view_image_only);
619 	if (view_image_only)
620 	{
621 		int i;
622 
623 		for (i = TOOLBAR_MAIN; i < TOOLBAR_MAX; i++)
624 			if (toolbar_boxes[i]) cmd_showhide(toolbar_boxes[i], FALSE);
625 	}
626 	else toolbar_showhide(); // Switch toolbar/status/palette on if needed
627 }
628 
zoom_grid(int state)629 static void zoom_grid(int state)
630 {
631 	mem_show_grid = state;
632 	update_stuff(UPD_RENDER);
633 }
634 
635 static void delete_event(main_dd *dt, void **wdata);
636 
quit_all(int mode)637 static void quit_all(int mode)
638 {
639 	if (mode || q_quit) delete_event(GET_DDATA(main_window_), main_window_);
640 }
641 
642 /* Scroll canvas under cursor instead of moving the cursor */
try_scroll(int * dxy)643 static int try_scroll(int *dxy)
644 {
645 	int bx, by, mx[2];
646 
647 	if (!arrow_scroll || mouse_left_canvas) return (TRUE); // Let cursor go
648 
649 	cmd_read(scrolledwindow_canvas, NULL);
650 	cmd_peekv(scrolledwindow_canvas, mx, sizeof(mx), CSCROLL_LIMITS);
651 	dxy[0] -= (bx = bounded(cvxy[0] + dxy[0], 0, mx[0])) - cvxy[0];
652 	dxy[1] -= (by = bounded(cvxy[1] + dxy[1], 0, mx[1])) - cvxy[1];
653 	if ((bx ^ cvxy[0]) | (by ^ cvxy[1]))
654 	{
655 		cvxy[0] = bx;
656 		cvxy[1] = by;
657 		cmd_reset(scrolledwindow_canvas, NULL);
658 	}
659 	return (dxy[0] | dxy[1]);
660 }
661 
662 /* Forward declaration */
663 static void mouse_event(mouse_ext *m, int mflag, int dx, int dy);
664 
665 /* For "dual" mouse control */
666 static int unreal_move, lastdx, lastdy;
667 
move_mouse(int dx,int dy,int button)668 static void move_mouse(int dx, int dy, int button)
669 {
670 	static unsigned int bmasks[4] = { 0, _B1mask, _B2mask, _B3mask };
671 	mouse_ext m;
672 	int dxy[2], zoom = 1, scale = 1;
673 
674 	if (!unreal_move) lastdx = lastdy = 0;
675 	if (!mem_img[CHN_IMAGE]) return;
676 	dx += lastdx; dy += lastdy;
677 
678 	cmd_peekv(drawing_canvas, &m, sizeof(m), CANVAS_FIND_MOUSE);
679 
680 	if (button) /* Clicks simulated without extra movements */
681 	{
682 		m.button = button;
683 		m.count = 1; // press
684 		mouse_event(&m, 1, dx, dy);
685 		m.state |= bmasks[button]; // Shows state _prior_ to event
686 		m.count = -1; // release
687 		mouse_event(&m, 1, dx, dy);
688 		return;
689 	}
690 
691 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
692 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
693 	else scale = rint(can_zoom);
694 
695 	if (zoom > 1) /* Fine control required */
696 	{
697 		lastdx = dx; lastdy = dy;
698 		mouse_event(&m, 1, dx, dy); // motion
699 		unreal_move = 2;
700 
701 		/* Nudge cursor when needed */
702 		if ((abs(lastdx) >= zoom) || (abs(lastdy) >= zoom))
703 		{
704 			lastdx -= (dxy[0] = lastdx * can_zoom) * zoom;
705 			lastdy -= (dxy[1] = lastdy * can_zoom) * zoom;
706 			if (try_scroll(dxy))
707 			{
708 				unreal_move = 3;
709 				/* Event can be delayed or lost */
710 				cmd_setv(drawing_canvas, dxy, CANVAS_BMOVE_MOUSE);
711 			}
712 		}
713 	}
714 	else /* Real mouse is precise enough */
715 	{
716 		unreal_move = 1;
717 
718 		/* Simulate movement unless actually moved mouse */
719 		dxy[0] = dx * scale;
720 		dxy[1] = dy * scale;
721 		while (TRUE)
722 		{
723 			if (try_scroll(dxy))
724 			{
725 				cmd_setv(drawing_canvas, dxy, CANVAS_BMOVE_MOUSE);
726 				if (!(dxy[0] | dxy[1])) break; // Moved OK
727 				lastdx = dxy[0] / scale;
728 				lastdy = dxy[1] / scale;
729 			}
730 			mouse_event(&m, 1, dx, dy); // motion
731 			break;
732 		}
733 	}
734 }
735 
stop_line()736 void stop_line()
737 {
738 	int i = line_status == LINE_LINE;
739 
740 	line_status = LINE_NONE;
741 	if (i) repaint_line(NULL);
742 }
743 
check_zoom_keys_real(int act_m)744 int check_zoom_keys_real(int act_m)
745 {
746 	int action = act_m >> 16;
747 
748 	if ((action == ACT_ZOOM) || (action == ACT_VIEW) || (action == ACT_VWZOOM))
749 	{
750 		action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
751 		return (TRUE);
752 	}
753 	return (FALSE);
754 }
755 
check_zoom_keys(int act_m)756 int check_zoom_keys(int act_m)
757 {
758 	int action = act_m >> 16;
759 
760 	if (check_zoom_keys_real(act_m)) return (TRUE);
761 	if ((action == ACT_DOCK) || (action == ACT_QUIT) ||
762 		(action == DLG_BRCOSA) || (action == ACT_PAN) ||
763 		(action == ACT_CROP) || (action == ACT_SWAP_AB) ||
764 		(action == DLG_CHOOSER) || (action == ACT_TOOL))
765 		action_dispatch(action, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
766 	else return (FALSE);
767 
768 	return (TRUE);
769 }
770 
771 static void menu_action(void *dt, void **wdata, int what, void **where);
772 
773 static void *keylist_code[] = {
774 	uMENUBAR(menu_action),
775 	uMENUITEM(_("Zoom in"), ACTMOD(ACT_ZOOM, 0)),
776 		SHORTCUT(plus, 0), SHORTCUT(KP_Add, 0),
777 	uMENUITEM(_("Zoom out"), ACTMOD(ACT_ZOOM, -1)),
778 		SHORTCUT(minus, 0), SHORTCUT(KP_Subtract, 0),
779 	uMENUITEM(_("10% zoom"), ACTMOD(ACT_ZOOM, -10)),
780 		SHORTCUT(1, 0), SHORTCUT(KP_1, 0),
781 	uMENUITEM(_("25% zoom"), ACTMOD(ACT_ZOOM, -4)),
782 		SHORTCUT(2, 0), SHORTCUT(KP_2, 0),
783 	uMENUITEM(_("50% zoom"), ACTMOD(ACT_ZOOM, -2)),
784 		SHORTCUT(3, 0), SHORTCUT(KP_3, 0),
785 	uMENUITEM(_("100% zoom"), ACTMOD(ACT_ZOOM, 1)),
786 		SHORTCUT(4, 0), SHORTCUT(KP_4, 0),
787 	uMENUITEM(_("400% zoom"), ACTMOD(ACT_ZOOM, 4)),
788 		SHORTCUT(5, 0), SHORTCUT(KP_5, 0),
789 	uMENUITEM(_("600% zoom"), ACTMOD(ACT_ZOOM, 6)),
790 		SHORTCUT0,
791 	uMENUITEM(_("800% zoom"), ACTMOD(ACT_ZOOM, 8)),
792 		SHORTCUT(6, 0), SHORTCUT(KP_6, 0),
793 	uMENUITEM(_("1000% zoom"), ACTMOD(ACT_ZOOM, 10)),
794 		SHORTCUT0,
795 	uMENUITEM(_("1200% zoom"), ACTMOD(ACT_ZOOM, 12)),
796 		SHORTCUT(7, 0), SHORTCUT(KP_7, 0),
797 	uMENUITEM(_("1600% zoom"), ACTMOD(ACT_ZOOM, 16)),
798 		SHORTCUT(8, 0), SHORTCUT(KP_8, 0),
799 	uMENUITEM(_("2000% zoom"), ACTMOD(ACT_ZOOM, 20)),
800 		SHORTCUT(9, 0), SHORTCUT(KP_9, 0),
801 	uMENUITEM(_("4000% zoom"), ACTMOD(ACT_ZOOM, 40)),
802 		SHORTCUT0,
803 	uMENUITEM(_("8000% zoom"), ACTMOD(ACT_ZOOM, 80)),
804 		SHORTCUT0,
805 	uMENUITEM(_("10% opacity"), ACTMOD(ACT_OPAC, 1)),
806 		SHORTCUT(1, C), SHORTCUT(KP_1, C),
807 	uMENUITEM(_("20% opacity"), ACTMOD(ACT_OPAC, 2)),
808 		SHORTCUT(2, C), SHORTCUT(KP_2, C),
809 	uMENUITEM(_("30% opacity"), ACTMOD(ACT_OPAC, 3)),
810 		SHORTCUT(3, C), SHORTCUT(KP_3, C),
811 	uMENUITEM(_("40% opacity"), ACTMOD(ACT_OPAC, 4)),
812 		SHORTCUT(4, C), SHORTCUT(KP_4, C),
813 	uMENUITEM(_("50% opacity"), ACTMOD(ACT_OPAC, 5)),
814 		SHORTCUT(5, C), SHORTCUT(KP_5, C),
815 	uMENUITEM(_("60% opacity"), ACTMOD(ACT_OPAC, 6)),
816 		SHORTCUT(6, C), SHORTCUT(KP_6, C),
817 	uMENUITEM(_("70% opacity"), ACTMOD(ACT_OPAC, 7)),
818 		SHORTCUT(7, C), SHORTCUT(KP_7, C),
819 	uMENUITEM(_("80% opacity"), ACTMOD(ACT_OPAC, 8)),
820 		SHORTCUT(8, C), SHORTCUT(KP_8, C),
821 	uMENUITEM(_("90% opacity"), ACTMOD(ACT_OPAC, 9)),
822 		SHORTCUT(9, C), SHORTCUT(KP_9, C),
823 	uMENUITEM(_("100% opacity"), ACTMOD(ACT_OPAC, 10)),
824 		SHORTCUT(0, C), SHORTCUT(KP_0, C),
825 	uMENUITEM(_("Increase opacity"), ACTMOD(ACT_OPAC, 0)),
826 		SHORTCUT(plus, C), SHORTCUT(KP_Add, C),
827 	uMENUITEM(_("Decrease opacity"), ACTMOD(ACT_OPAC, -1)),
828 		SHORTCUT(minus, C), SHORTCUT(KP_Subtract, C),
829 	uMENUITEM(_("Draw open arrow head"), ACTMOD(ACT_ARROW, 2)),
830 		SHORTCUT(a, 0),
831 	uMENUITEM(_("Draw closed arrow head"), ACTMOD(ACT_ARROW, 3)),
832 		SHORTCUT(s, 0),
833 	uMENUITEM(_("Previous colour A"), ACTMOD(ACT_A, -1)),
834 		SHORTCUT(bracketleft, 0),
835 	uMENUITEM(_("Next colour A"), ACTMOD(ACT_A, 1)),
836 		SHORTCUT(bracketright, 0),
837 	uMENUITEM(_("Previous colour B"), ACTMOD(ACT_B, -1)),
838 		SHORTCUT(bracketleft, S), SHORTCUT(braceleft, S),
839 	uMENUITEM(_("Next colour B"), ACTMOD(ACT_B, 1)),
840 		SHORTCUT(bracketright, S), SHORTCUT(braceright, S),
841 	uMENUITEM(_("Previous pattern"), ACTMOD(ACT_PAT, -1)),
842 		SHORTCUT0,
843 	uMENUITEM(_("Next pattern"), ACTMOD(ACT_PAT, 1)),
844 		SHORTCUT0,
845 	uMENUITEM(_("Larger brush"), ACTMOD(ACT_SIZE, 1)),
846 		SHORTCUT0,
847 	uMENUITEM(_("Smaller brush"), ACTMOD(ACT_SIZE, -1)),
848 		SHORTCUT0,
849 	uMENUITEM(_("View window - Zoom in"), ACTMOD(ACT_VWZOOM, 0)),
850 		SHORTCUT(plus, S), SHORTCUT(KP_Add, S),
851 	uMENUITEM(_("View window - Zoom out"), ACTMOD(ACT_VWZOOM, -1)),
852 		SHORTCUT(minus, S), SHORTCUT(KP_Subtract, S),
853 ///	FIXED KEYS
854 	uMENUITEM(NULL, ACTMOD(ACT_QUIT, 0)),
855 		SHORTCUT(q, 0), SHORTCUT(q, S), SHORTCUT(q, A),
856 		SHORTCUT(q, CS), SHORTCUT(q, CA),
857 		SHORTCUT(q, SA), SHORTCUT(q, CSA),
858 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 5)),
859 		SHORTCUT(Left, S), SHORTCUT(KP_Left, S),
860 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 7)),
861 		SHORTCUT(Right, S), SHORTCUT(KP_Right, S),
862 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 3)),
863 		SHORTCUT(Down, S), SHORTCUT(KP_Down, S),
864 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 9)),
865 		SHORTCUT(Up, S), SHORTCUT(KP_Up, S),
866 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 4)),
867 		SHORTCUT(Left, 0), SHORTCUT(KP_Left, 0),
868 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 6)),
869 		SHORTCUT(Right, 0), SHORTCUT(KP_Right, 0),
870 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 2)),
871 		SHORTCUT(Down, 0), SHORTCUT(KP_Down, 0),
872 	uMENUITEM(NULL, ACTMOD(ACT_SEL_MOVE, 8)),
873 		SHORTCUT(Up, 0), SHORTCUT(KP_Up, 0),
874 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 5)),
875 		SHORTCUT(Left, CS), SHORTCUT(KP_Left, CS),
876 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 7)),
877 		SHORTCUT(Right, CS), SHORTCUT(KP_Right, CS),
878 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 3)),
879 		SHORTCUT(Down, CS), SHORTCUT(KP_Down, CS),
880 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 9)),
881 		SHORTCUT(Up, CS), SHORTCUT(KP_Up, CS),
882 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 4)),
883 		SHORTCUT(Left, C), SHORTCUT(KP_Left, C),
884 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 6)),
885 		SHORTCUT(Right, C), SHORTCUT(KP_Right, C),
886 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 2)),
887 		SHORTCUT(Down, C), SHORTCUT(KP_Down, C),
888 	uMENUITEM(NULL, ACTMOD(ACT_LR_MOVE, 8)),
889 		SHORTCUT(Up, C), SHORTCUT(KP_Up, C),
890 	uMENUITEM(NULL, ACTMOD(ACT_ESC, 0)),
891 		SHORTCUT(Escape, 0), SHORTCUT(Escape, A),
892 	uMENUITEM(NULL, ACTMOD(ACT_COMMIT, 0)),
893 		SHORTCUT(Return, 0), SHORTCUT(Return, C),
894 		SHORTCUT(Return, A), SHORTCUT(Return, CA),
895 		SHORTCUT(KP_Enter, 0), SHORTCUT(KP_Enter, C),
896 		SHORTCUT(KP_Enter, A), SHORTCUT(KP_Enter, CA),
897 	uMENUITEM(NULL, ACTMOD(ACT_COMMIT, 1)),
898 		SHORTCUT(Return, S), SHORTCUT(Return, CS),
899 		SHORTCUT(Return, SA), SHORTCUT(Return, CSA),
900 		SHORTCUT(KP_Enter, S), SHORTCUT(KP_Enter, CS),
901 		SHORTCUT(KP_Enter, SA), SHORTCUT(KP_Enter, CSA),
902 	uMENUITEM(NULL, ACTMOD(ACT_RCLICK, 0)),
903 		SHORTCUT(BackSpace, 0), SHORTCUT(BackSpace, C),
904 		SHORTCUT(BackSpace, S), SHORTCUT(BackSpace, A),
905 		SHORTCUT(BackSpace, CS), SHORTCUT(BackSpace, CA),
906 		SHORTCUT(BackSpace, SA), SHORTCUT(BackSpace, CSA),
907 	RET
908 };
909 
key_action(key_ext * key,int toggle)910 int key_action(key_ext *key, int toggle)
911 {
912 	main_dd *dt = GET_DDATA(main_window_);
913 	void *v, **slot;
914 	int act_m;
915 
916 	cmd_setv(main_keys, key, KEYMAP_KEY);
917 	slot = dt->keyslot;
918 	// Leave unmapped key to be handled elsewhere
919 	if (!slot) act_m = 0;
920 	// Do nothing if slot is insensitive
921 	else if (!cmd_checkv(slot, SLOT_SENSITIVE)) act_m = ACTMOD_DUMMY;
922 	// Activate toggleable widget if allowed
923 	else if (toggle && (v = slot_data(slot, dt)))
924 	{
925 		cmd_set(slot, cmd_checkv(slot, SLOT_RADIO) || !*(int *)v);
926 		act_m = ACTMOD_DUMMY;
927 	}
928 	else act_m = TOOL_ID(slot);
929 	return (act_m);
930 }
931 
dock_focused()932 int dock_focused()
933 {
934 	return (cmd_checkv(dock_book, SLOT_FOCUSED));
935 }
936 
handle_keypress(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)937 static int handle_keypress(main_dd *dt, void **wdata, int what, void **where,
938 	key_ext *keydata)
939 {
940 	int act_m = key_action(keydata, TRUE);
941 
942 	if (!act_m) return (FALSE);
943 
944 	if (act_m != ACTMOD_DUMMY)
945 		action_dispatch(act_m >> 16, (act_m & 0xFFFF) - 0x8000, 0, TRUE);
946 	return (TRUE);
947 }
948 
draw_arrow(int mode)949 static void draw_arrow(int mode)
950 {
951 	int i, xa1, xa2, ya1, ya2, minx, maxx, miny, maxy, w, h;
952 	double uvx, uvy;	// Line length & unit vector lengths
953 	int oldmode = mem_undo_opacity;
954 	grad_info svgrad;
955 
956 
957 	if (!((tool_type == TOOL_LINE) && (line_status != LINE_NONE) &&
958 		((line_x1 != line_x2) || (line_y1 != line_y2)))) return;
959 	svgrad = gradient[mem_channel];
960 	line_to_gradient();
961 
962 	// Calculate 2 coords for arrow corners
963 	uvy = sqrt((line_x1 - line_x2) * (line_x1 - line_x2) +
964 		(line_y1 - line_y2) * (line_y1 - line_y2));
965 	uvx = (line_x1 - line_x2) / uvy;
966 	uvy = (line_y1 - line_y2) / uvy;
967 
968 	xa1 = rint(line_x2 + tool_flow * (uvx - uvy * 0.5));
969 	xa2 = rint(line_x2 + tool_flow * (uvx + uvy * 0.5));
970 	ya1 = rint(line_y2 + tool_flow * (uvy + uvx * 0.5));
971 	ya2 = rint(line_y2 + tool_flow * (uvy - uvx * 0.5));
972 
973 // !!! Call this, or let undo engine do it?
974 //	mem_undo_prepare();
975 	pen_down = 0;
976 	do_tool_action(TC_LINE_ARROW, line_x2, line_y2, MAX_PRESSURE);
977 	line_status = LINE_LINE;
978 
979 	// Draw arrow lines & circles
980 	mem_undo_opacity = TRUE;
981 	f_circle(xa1, ya1, tool_size);
982 	f_circle(xa2, ya2, tool_size);
983 	tline(xa1, ya1, line_x2, line_y2, tool_size);
984 	tline(xa2, ya2, line_x2, line_y2, tool_size);
985 
986 	if (mode == 3)
987 	{
988 		// Draw 3rd line and fill arrowhead
989 		tline(xa1, ya1, xa2, ya2, tool_size );
990 		poly_points = 0;
991 		poly_add(line_x2, line_y2);
992 		poly_add(xa1, ya1);
993 		poly_add(xa2, ya2);
994 		poly_paint();
995 		poly_points = 0;
996 	}
997 	mem_undo_opacity = oldmode;
998 	gradient[mem_channel] = svgrad;
999 	mem_undo_prepare();
1000 	pen_down = 0;
1001 
1002 	// Update screen areas
1003 	minx = xa1 < xa2 ? xa1 : xa2;
1004 	if (minx > line_x2) minx = line_x2;
1005 	maxx = xa1 > xa2 ? xa1 : xa2;
1006 	if (maxx < line_x2) maxx = line_x2;
1007 
1008 	miny = ya1 < ya2 ? ya1 : ya2;
1009 	if (miny > line_y2) miny = line_y2;
1010 	maxy = ya1 > ya2 ? ya1 : ya2;
1011 	if (maxy < line_y2) maxy = line_y2;
1012 
1013 	i = (tool_size + 1) >> 1;
1014 	minx -= i; miny -= i; maxx += i; maxy += i;
1015 
1016 	w = maxx - minx + 1;
1017 	h = maxy - miny + 1;
1018 
1019 	update_stuff(UPD_IMGP);
1020 	lr_update_area(layer_selected, minx, miny, w, h);
1021 }
1022 
check_for_changes()1023 int check_for_changes()			// 1=STOP, 2=IGNORE, -10=NOT CHANGED
1024 {
1025 	if (!mem_changed) return (-10);
1026 	return (alert_box(_("Warning"),
1027 		_("This canvas/palette contains changes that have not been saved.  Do you really want to lose these changes?"),
1028 		_("Cancel Operation"), _("Lose Changes"), NULL));
1029 }
1030 
var_init()1031 void var_init()
1032 {
1033 	inilist *ilp;
1034 
1035 	/* Load listed settings */
1036 	for (ilp = ini_bool; ilp->name; ilp++)
1037 		*(ilp->var) = inifile_get_gboolean(ilp->name, ilp->defv);
1038 	for (ilp = ini_int; ilp->name; ilp++)
1039 		*(ilp->var) = inifile_get_gint32(ilp->name, ilp->defv);
1040 
1041 	/* Initialize undo memory space */
1042 	if (mem_undo_limit <= 0)
1043 	{
1044 		unsigned mem = sys_mem_size();
1045 		/* Limit usable space to 2 Gb on 32-bit systems */
1046 		if ((sizeof(void *) <= 4) && (mem > 2048)) mem = 2048;
1047 		/* Take 1/4 of memory space, rounded up to nearest 32 Mb */
1048 		mem_undo_limit = ((mem / 4) + 31) & ~31;
1049 		/* But no less than 32 Mb */
1050 		if (!mem_undo_limit) mem_undo_limit = 32;
1051 	}
1052 
1053 #ifdef U_TIFF
1054 	/* Load TIFF types */
1055 	{
1056 		int i, tr, ti, tb;
1057 		tr = inifile_get_gint32("tiffTypeRGB", 1 /* COMPRESSION_NONE */);
1058 		ti = inifile_get_gint32("tiffTypeI", 1 /* COMPRESSION_NONE */);
1059 		tb = inifile_get_gint32("tiffTypeBW", 1 /* COMPRESSION_NONE */);
1060 		init_tiff_formats();
1061 		for (i = 0; tiff_formats[i].name; i++)
1062 		{
1063 			if (tiff_formats[i].id == tr) tiff_rtype = i;
1064 			if (tiff_formats[i].id == ti) tiff_itype = i;
1065 			if (tiff_formats[i].id == tb) tiff_btype = i;
1066 		}
1067 	}
1068 #endif
1069 }
1070 
string_init()1071 void string_init()
1072 {
1073 	int i;
1074 
1075 	for (i = 0; i < NUM_CHANNELS + 1; i++)
1076 		allchannames[i] = channames[i] = __(channames_[i]);
1077 	channames[CHN_IMAGE] = "";
1078 	for (i = 0; i < NUM_CSPACES; i++)
1079 		cspnames[i] = __(cspnames_[i]);
1080 }
1081 
1082 static void toggle_dock(int state);
1083 
delete_event(main_dd * dt,void ** wdata)1084 static void delete_event(main_dd *dt, void **wdata)
1085 {
1086 	inilist *ilp;
1087 	int i;
1088 
1089 	i = layers_total ? check_layers_for_changes() : check_for_changes();
1090 	if (i == -10)
1091 	{
1092 		i = 2;
1093 		if (inifile_get_gboolean("exitToggle", FALSE))
1094 			i = alert_box(MT_VERSION, _("Do you really want to quit?"),
1095 				_("NO"), _("YES"), NULL);
1096 	}
1097 	if (i != 2) return; // Cancel quitting
1098 
1099 	/* Store listed settings */
1100 	for (ilp = ini_bool; ilp->name; ilp++)
1101 		inifile_set_gboolean(ilp->name, *(ilp->var));
1102 	for (ilp = ini_int; ilp->name; ilp++)
1103 		inifile_set_gint32(ilp->name, *(ilp->var));
1104 
1105 #ifdef U_TIFF
1106 	/* Store TIFF types */
1107 	inifile_set_gint32("tiffTypeRGB", tiff_formats[tiff_rtype].id);
1108 	inifile_set_gint32("tiffTypeI", tiff_formats[tiff_itype].id);
1109 	inifile_set_gint32("tiffTypeBW", tiff_formats[tiff_btype].id);
1110 #endif
1111 
1112 	update_recent_files(TRUE); // Store the list
1113 
1114 	if (files_passed <= 1) inifile_set_gboolean("showDock", show_dock);
1115 	toggle_dock(FALSE); // To remember dock size
1116 
1117 	// Get rid of extra windows + remember positions
1118 	delete_layers_window();
1119 
1120 	// Remember the toolbar settings
1121 	toolbar_settings_exit(NULL, NULL);
1122 
1123 	run_destroy(wdata);
1124 }
1125 
canvas_scroll(main_dd * dt,void ** wdata,int what,void ** where,scroll_ext * scroll)1126 static void canvas_scroll(main_dd *dt, void **wdata, int what, void **where,
1127 	scroll_ext *scroll)
1128 {
1129 	if (scroll_zoom)
1130 	{
1131 		action_dispatch(origin_slot(where) == drawing_canvas ?
1132 			ACT_ZOOM : ACT_VWZOOM, scroll->yscroll > 0 ? -1 : 0,
1133 			FALSE, TRUE);
1134 		scroll->xscroll = scroll->yscroll = 0;
1135 	}
1136 	else if (scroll->state & _Cmask) /* Convert up-down into left-right */
1137 	{
1138 		scroll->xscroll = scroll->yscroll;
1139 		scroll->yscroll = 0;
1140 	}
1141 }
1142 
1143 
grad_stroke(int x,int y)1144 void grad_stroke(int x, int y)
1145 {
1146 	grad_info *grad = gradient + mem_channel;
1147 	double d, stroke;
1148 	int i, j;
1149 
1150 	/* No usable gradient */
1151 	if (grad->wmode == GRAD_MODE_NONE) return;
1152 
1153 	if (!pen_down || (tool_type > TOOL_SPRAY)) /* Begin stroke */
1154 	{
1155 		grad_path = grad->xv = grad->yv = 0.0;
1156 	}
1157 	else /* Continue stroke */
1158 	{
1159 		i = x - tool_ox; j = y - tool_oy;
1160 		stroke = sqrt(i * i + j * j);
1161 		/* First step - anchor rear end */
1162 		if (grad_path == 0.0)
1163 		{
1164 			d = tool_size * 0.5 / stroke;
1165 			grad_x0 = tool_ox - i * d;
1166 			grad_y0 = tool_oy - j * d;
1167 		}
1168 		/* Scalar product */
1169 		d = (tool_ox - grad_x0) * (x - tool_ox) +
1170 			(tool_oy - grad_y0) * (y - tool_oy);
1171 		if (d < 0.0) /* Going backward - flip rear */
1172 		{
1173 			d = tool_size * 0.5 / stroke;
1174 			grad_x0 = x - i * d;
1175 			grad_y0 = y - j * d;
1176 			grad_path += tool_size + stroke;
1177 		}
1178 		else /* Going forward or sideways - drag rear */
1179 		{
1180 			stroke = sqrt((x - grad_x0) * (x - grad_x0) +
1181 				(y - grad_y0) * (y - grad_y0));
1182 			d = tool_size * 0.5 / stroke;
1183 			grad_x0 = x + (grad_x0 - x) * d;
1184 			grad_y0 = y + (grad_y0 - y) * d;
1185 			grad_path += stroke - tool_size * 0.5;
1186 		}
1187 		d = 2.0 / (double)tool_size;
1188 		grad->xv = (x - grad_x0) * d;
1189 		grad->yv = (y - grad_y0) * d;
1190 	}
1191 }
1192 
1193 #define GF_UPDATE 1
1194 #define GF_REDRAW 2
1195 #define GF_DRAW   4
1196 
do_grad_action(int cmd,int x,int y)1197 static void do_grad_action(int cmd, int x, int y)
1198 {
1199 	grad_info *grad = gradient + mem_channel;
1200 	int old[4], prev = grad->status, upd = 0;
1201 
1202 	copy4(old, grad->xy);
1203 	switch (cmd)
1204 	{
1205 	case TC_GRAD_START:
1206 		grad->xy[0] = grad->xy[2] = x;
1207 		grad->xy[1] = grad->xy[3] = y;
1208 		grad->status = GRAD_END;
1209 		upd = GF_UPDATE | GF_DRAW;
1210 		break;
1211 	case TC_GRAD_PICK0:
1212 		grad->status = GRAD_START;
1213 		// Fallthrough
1214 	case TC_GRAD_SET0:
1215 		grad->xy[0] = x;
1216 		grad->xy[1] = y;
1217 		if (cmd == TC_GRAD_SET0) prev = grad->status = GRAD_DONE;
1218 		upd = GF_UPDATE | GF_REDRAW;
1219 		break;
1220 	case TC_GRAD_PICK1:
1221 		grad->status = GRAD_END;
1222 		// Fallthrough
1223 	case TC_GRAD_SET1:
1224 		grad->xy[2] = x;
1225 		grad->xy[3] = y;
1226 		if (cmd == TC_GRAD_SET1) prev = grad->status = GRAD_DONE;
1227 		upd = GF_UPDATE | GF_REDRAW;
1228 		break;
1229 	case TC_GRAD_DRAG0: case TC_GRAD_DRAG1:
1230 	{
1231 		int *xy = grad->xy + (cmd == TC_GRAD_DRAG0 ? 0 : 2);
1232 		if ((xy[0] != x) || (xy[1] != y))
1233 		{
1234 			xy[0] = x;
1235 			xy[1] = y;
1236 			upd = GF_UPDATE | GF_REDRAW;
1237 		}
1238 		break;
1239 	}
1240 	case TC_GRAD_CLEAR:
1241 		grad->status = GRAD_NONE;
1242 		upd = GF_UPDATE | GF_DRAW;
1243 		break;
1244 	}
1245 
1246 	if (upd & GF_UPDATE) grad_update(grad);
1247 	if (upd & (GF_REDRAW | GF_DRAW))
1248 	{
1249 		if ((prev == GRAD_DONE) && grad_opacity)
1250 			cmd_repaint(drawing_canvas);
1251 		else repaint_grad(upd & GF_REDRAW ? old : NULL);
1252 	}
1253 }
1254 
grad_action(int count,int button,int x,int y)1255 static int grad_action(int count, int button, int x, int y)
1256 {
1257 	grad_info *grad = gradient + mem_channel;
1258 	int i, j, cmd = TC_NONE;
1259 
1260 	/* Left click sets points and picks them up again */
1261 	if ((count == 1) && (button == 1))
1262 	{
1263 		/* Start anew */
1264 		if (grad->status == GRAD_NONE) cmd = TC_GRAD_START;
1265 		/* Place starting point */
1266 		else if (grad->status == GRAD_START) cmd = TC_GRAD_SET0;
1267 		/* Place end point */
1268 		else if (grad->status == GRAD_END) cmd = TC_GRAD_SET1;
1269 		/* Pick up nearest end */
1270 		else if (grad->status == GRAD_DONE)
1271 		{
1272 			i = (x - grad->xy[0]) * (x - grad->xy[0]) +
1273 				(y - grad->xy[1]) * (y - grad->xy[1]);
1274 			j = (x - grad->xy[2]) * (x - grad->xy[2]) +
1275 				(y - grad->xy[3]) * (y - grad->xy[3]);
1276 			cmd = i < j ? TC_GRAD_PICK0 : TC_GRAD_PICK1;
1277 		}
1278 	}
1279 
1280 	/* Everything but left click is irrelevant when no gradient */
1281 	else if (grad->status == GRAD_NONE);
1282 
1283 	/* Right click deletes the gradient */
1284 	else if (count == 1) cmd = TC_GRAD_CLEAR; /* button != 1 */
1285 
1286 	/* Motion is irrelevant with gradient in place */
1287 	else if (grad->status == GRAD_DONE);
1288 
1289 	/* Motion drags points around */
1290 	else if (!count)
1291 		cmd = grad->status == GRAD_START ? TC_GRAD_DRAG0 : TC_GRAD_DRAG1;
1292 
1293 	return (cmd);
1294 }
1295 
1296 /* Pick color A/B from canvas, or use one given */
pick_color(int ox,int oy,int ab,int area[4],int pixel)1297 static int pick_color(int ox, int oy, int ab, int area[4], int pixel)
1298 {
1299 	int rgba = RGBA_mode && (mem_channel == CHN_IMAGE);
1300 	int upd = 0, alpha = 255; // opaque
1301 
1302 	if (pixel < 0) // If no pixel value is given
1303 	{
1304 		/* Default alpha */
1305 		alpha = channel_col_[ab][CHN_ALPHA];
1306 		/* Average brush or selection area on double click */
1307 		while (area)
1308 		{
1309 			int rect[4];
1310 
1311 			/* Clip to image */
1312 			copy4(rect, area);
1313 			rect[2] += rect[0];
1314 			rect[3] += rect[1];
1315 			if (!clip(rect, 0, 0, mem_width, mem_height, rect)) break;
1316 			/* Average utility channel */
1317 			if (mem_channel != CHN_IMAGE) pixel = average_channel(
1318 				mem_img[mem_channel], mem_width, rect);
1319 			if (MEM_BPP != 3) break;
1320 			/* Average alpha if needed */
1321 			if (rgba && mem_img[CHN_ALPHA]) alpha = average_channel(
1322 				mem_img[CHN_ALPHA], mem_width, rect);
1323 			/* Average image channel as RGBA or RGB */
1324 			pixel = average_pixels(mem_img[CHN_IMAGE],
1325 				rgba && alpha && !channel_dis[CHN_ALPHA] ?
1326 				mem_img[CHN_ALPHA] : NULL, mem_width, rect);
1327 			break;
1328 		}
1329 		/* Failing that, just pick color from image */
1330 		if (pixel < 0)
1331 		{
1332 			pixel = get_pixel(ox, oy);
1333 			if (mem_img[CHN_ALPHA])
1334 				alpha = mem_img[CHN_ALPHA][mem_width * oy + ox];
1335 		}
1336 	}
1337 
1338 	if (rgba)
1339 	{
1340 		upd = channel_col_[ab][CHN_ALPHA] ^ alpha;
1341 		channel_col_[ab][CHN_ALPHA] = alpha;
1342 	}
1343 
1344 	if (mem_channel != CHN_IMAGE)
1345 	{
1346 		upd |= channel_col_[ab][mem_channel] ^ pixel;
1347 		channel_col_[ab][mem_channel] = pixel;
1348 	}
1349 	else if (mem_img_bpp == 1)
1350 	{
1351 		upd |= mem_col_[ab] ^ pixel;
1352 		mem_col_[ab] = pixel;
1353 		mem_col_24[ab] = mem_pal[pixel];
1354 	}
1355 	else
1356 	{
1357 		png_color *col = mem_col_24 + ab;
1358 
1359 		upd |= PNG_2_INT(*col) ^ pixel;
1360 		col->red = INT_2_R(pixel);
1361 		col->green = INT_2_G(pixel);
1362 		col->blue = INT_2_B(pixel);
1363 	}
1364 	return (upd);
1365 }
1366 
tool_done()1367 static void tool_done()
1368 {
1369 	tint_mode[2] = 0; // Paranoia
1370 	clone_status &= CLONE_ABS;
1371 	pen_down = 0;
1372 	if (col_reverse)
1373 	{
1374 		col_reverse = FALSE;
1375 		mem_swap_cols(FALSE);
1376 	}
1377 
1378 	mem_undo_prepare();
1379 }
1380 
1381 static int get_bkg(int xc, int yc, int dclick);
1382 
xmmod(int x,int y)1383 static inline int xmmod(int x, int y)
1384 {
1385 	return (x - (x % y));
1386 }
1387 
1388 /* Mouse event from button/motion on the canvas */
mouse_event(mouse_ext * m,int mflag,int dx,int dy)1389 static void mouse_event(mouse_ext *m, int mflag, int dx, int dy)
1390 {
1391 	static int tool_fix, tool_fixv;	// Fixate on axis
1392 	int new_cursor;
1393 	int i, x0, y0, ox, oy;
1394 	int x, y, cmd;
1395 	int zoom = 1, scale = 1;
1396 
1397 
1398 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1399 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
1400 	else scale = rint(can_zoom);
1401 
1402 	x0 = floor_div((m->x - margin_main_x) * zoom, scale) + dx;
1403 	y0 = floor_div((m->y - margin_main_y) * zoom, scale) + dy;
1404 
1405 	ox = x0 < 0 ? 0 : x0 >= mem_width ? mem_width - 1 : x0;
1406 	oy = y0 < 0 ? 0 : y0 >= mem_height ? mem_height - 1 : y0;
1407 
1408 	if (!mflag) /* Coordinate fixation */
1409 	{
1410 		if (!(m->state & _Smask)) tool_fix = 0;
1411 		else if (!(m->state & _Cmask)) /* Shift */
1412 		{
1413 			if (tool_fix != 1) tool_fixv = x0;
1414 			tool_fix = 1;
1415 		}
1416 		else /* Ctrl+Shift */
1417 		{
1418 			if (tool_fix != 2) tool_fixv = y0;
1419 			tool_fix = 2;
1420 		}
1421 	}
1422 	/* No use when moving cursor by keyboard */
1423 	else if (!m->count) tool_fix = 0;
1424 
1425 	if (!tool_fix);
1426 	/* For rectangular selection it makes sense to fix its width/height */
1427 	else if ((tool_type == TOOL_SELECT) && (m->button == 1) &&
1428 		((marq_status == MARQUEE_DONE) || (marq_status == MARQUEE_SELECTING)))
1429 	{
1430 		if (tool_fix == 1) x0 = marq_x2;
1431 		else y0 = marq_y2;
1432 	}
1433 	else if (tool_fix == 1) x0 = tool_fixv;
1434 	else /* if (tool_fix == 2) */ y0 = tool_fixv;
1435 
1436 	x = x0; y = y0;
1437 	if (tgrid_snap) /* Snap to grid */
1438 	{
1439 		int xy[2] = { x0, y0 };
1440 
1441 		/* For everything but rectangular selection, snap with rounding
1442 		 * feels more natural than with flooring - WJ */
1443 		if (tool_type != TOOL_SELECT)
1444 		{
1445 			xy[0] += tgrid_dx >> 1;
1446 			xy[1] += tgrid_dy >> 1;
1447 		}
1448 		snap_xy(xy);
1449 		if ((x = x0 = xy[0]) < 0) x += xmmod(tgrid_dx - x - 1, tgrid_dx);
1450 		if (x >= mem_width) x -= xmmod(tgrid_dx + x - mem_width, tgrid_dx);
1451 		if ((y = y0 = xy[1]) < 0) y += xmmod(tgrid_dy - y - 1, tgrid_dy);
1452 		if (y >= mem_height) y -= xmmod(tgrid_dy + y - mem_height, tgrid_dy);
1453 	}
1454 
1455 	x = x < 0 ? 0 : x >= mem_width ? mem_width - 1 : x;
1456 	y = y < 0 ? 0 : y >= mem_height ? mem_height - 1 : y;
1457 
1458 	/* ****** Release-event-specific code ****** */
1459 
1460 	if (m->count < 0)
1461 	{
1462 		cmd = TC_NONE;
1463 		if ((tool_type == TOOL_LINE) && (m->button == 1) &&
1464 			(line_status == LINE_START)) cmd = TC_LINE_NEXT;
1465 
1466 		if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON)) &&
1467 			(m->button == 1) && ((marq_status == MARQUEE_SELECTING) ||
1468 			(marq_status == MARQUEE_PASTE_DRAG))) cmd = TC_SEL_STOP;
1469 
1470 		// Finish off dragged polygon selection
1471 		if ((tool_type == TOOL_POLYGON) && (poly_status == POLY_DRAGGING))
1472 			cmd = TC_POLY_CLOSE;
1473 
1474 		if (cmd != TC_NONE) do_tool_action(cmd, x, y, 0);
1475 
1476 		tool_done();
1477 		update_menus();
1478 
1479 		return;
1480 	}
1481 
1482 	/* ****** Common click/motion handling code ****** */
1483 
1484 	if ((m->state & _CSmask) == _Cmask)
1485 	{
1486 		/* Delete point from polygon */
1487 		if ((m->button == 3) && (tool_type == TOOL_POLYGON))
1488 		{
1489 			if (m->count == 1) do_tool_action(TC_POLY_DEL, x, y, 0);
1490 		}
1491 		else if (m->button == 2) /* Auto-dither */
1492 		{
1493 			if ((mem_channel == CHN_IMAGE) && (mem_img_bpp == 3))
1494 				pressed_dither_A();
1495 		}
1496 		/* Snap clone source to point */
1497 		else if ((m->button == 1) && (tool_type == TOOL_CLONE))
1498 		{
1499 			clone_x = x;
1500 			clone_y = y;
1501 			clone_dx = clone_dy = 0;
1502 			move_perim(x, y);
1503 		}
1504 		/* Set colour A/B */
1505 		else if ((m->button == 1) || (m->button == 3))
1506 		{
1507 			int pix, rect[4];
1508 
1509 			/* Pick color from tracing image when possible */
1510 			pix = get_bkg(m->x + dx * scale, m->y + dy * scale,
1511 				m->count == 2);
1512 
1513 			/* Double click averages an area */
1514 			rect[2] = 0;
1515 			if (pix >= 0); // Got it from tracing image
1516 			else if (m->count != 2); // No double click
1517 			// Have brush square
1518 			else if (!NO_PERIM(tool_type))
1519 			{
1520 				int ts2 = tool_size >> 1;
1521 				rect[0] = ox - ts2; rect[1] = oy - ts2;
1522 				rect[2] = rect[3] = tool_size;
1523 			}
1524 			// Have selection marquee
1525 			else if ((marq_status > MARQUEE_NONE) &&
1526 				(marq_status < MARQUEE_PASTE)) marquee_at(rect);
1527 
1528 			if (pick_color(ox, oy,
1529 				m->button == 3, // A for left, B for right
1530 				rect[2] ? rect : NULL, // Area to average
1531 				pix)) update_stuff(UPD_CAB);
1532 		}
1533 	}
1534 
1535 	else if ((m->button == 2) || ((m->button == 3) && (m->state & _Smask)))
1536 		set_zoom_centre(ox, oy);
1537 
1538 	else if (tool_type == TOOL_GRADIENT)
1539 	{
1540 		cmd = grad_action(m->count, m->button, x0, y0);
1541 		if (cmd != TC_NONE) do_grad_action(cmd, x0, y0);
1542 	}
1543 
1544 	/* Pure moves are handled elsewhere */
1545 	else if (m->button)
1546 	{
1547 		cmd = tool_action(m->count, m->button, x, y);
1548 		if (cmd != TC_NONE)
1549 			do_tool_action(cmd | TCF_ONCE, x, y, m->pressure);
1550 	}
1551 
1552 
1553 	/* ****** Now to mouse-move-specific part ****** */
1554 
1555 	/* A focus change caused by a click results in leave and enter events
1556 	 * immediately followed by a click event, so the task to restore the
1557 	 * tracking state then by necessity falls to that click - WJ */
1558 	if (!m->count || !is_tracking)
1559 	{
1560 		if ((poly_status == POLY_SELECTING) && !m->button)
1561 			stretch_poly_line(x, y);
1562 
1563 		if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON))
1564 		{
1565 			if (marq_status == MARQUEE_DONE)
1566 			{
1567 				i = close_to(x, y);
1568 				if (i != cursor_corner) // Stops excessive CPU/flickering
1569 					set_cursor(corner_cursor[cursor_corner = i]);
1570 			}
1571 			if (marq_status >= MARQUEE_PASTE)
1572 			{
1573 				new_cursor = (x >= marq_x1) && (x <= marq_x2) &&
1574 					(y >= marq_y1) && (y <= marq_y2); // Normal/4way
1575 				if (new_cursor != cursor_corner) // Stops flickering on slow hardware
1576 					set_cursor((cursor_corner = new_cursor) ?
1577 						move_cursor : NULL);
1578 			}
1579 		}
1580 		update_xy_bar(x, y);
1581 
1582 		/* TOOL PERIMETER BOX UPDATES */
1583 
1584 		if ((tool_type == TOOL_CLONE) && !(clone_status & CLONE_DRAG))
1585 		{
1586 			if ((clone_status == CLONE_ABS) ||
1587 				(clone_status == (CLONE_REL | CLONE_TRACK)))
1588 			{
1589 				/* Source stays put */
1590 				clone_dx = clone_x - x;
1591 				clone_dy = clone_y - y;
1592 			}
1593 			else
1594 			{
1595 				/* Source follows cursor */
1596 				clone_x = x + clone_dx;
1597 				clone_y = y + clone_dy;
1598 			}
1599 			clone_status = (!m->button && (m->state & _Cmask) ?
1600 				CLONE_TRACK : 0) | (clone_status & CLONE_ABS);
1601 		}
1602 
1603 		move_perim(x, y);
1604 
1605 		/* LINE UPDATES */
1606 
1607 		if ((tool_type == TOOL_LINE) && (line_status == LINE_LINE) &&
1608 			((line_x2 != x) || (line_y2 != y)))
1609 		{
1610 			int old[4];
1611 
1612 			copy4(old, line_xy);
1613 			line_x2 = x;
1614 			line_y2 = y;
1615 			repaint_line(old);
1616 		}
1617 
1618 		is_tracking = TRUE;
1619 	}
1620 
1621 	update_sel_bar(FALSE);
1622 }
1623 
canvas_mouse(main_dd * dt,void ** wdata,int what,void ** where,mouse_ext * mouse)1624 static int canvas_mouse(main_dd *dt, void **wdata, int what, void **where,
1625 	mouse_ext *mouse)
1626 {
1627 	int rm = unreal_move;
1628 
1629 	mouse_left_canvas = FALSE;
1630 
1631 	/* Steal focus from dock window */
1632 	if ((mouse->count > 0) && dock_focused())
1633 		cmd_setv(main_window_, NULL, WINDOW_FOCUS);
1634 
1635 	/* Skip synthetic mouse moves */
1636 	if (!mouse->count)
1637 	{
1638 		if (unreal_move == 3)
1639 		{
1640 			unreal_move = 2;
1641 			return (TRUE);
1642 		}
1643 		unreal_move = 0;
1644 	}
1645 
1646 	/* Do nothing if no image */
1647 	if ((mouse->count >= 0) && !mem_img[CHN_IMAGE]) return (TRUE);
1648 
1649 	/* If cursor got warped, will have another movement event to handle */
1650 	if (!mouse->count && mouse->button && (tool_type == TOOL_SELECT) &&
1651 		cmd_checkv(where, MOUSE_BOUND)) return (TRUE);
1652 
1653 	mouse_event(mouse, rm & 1, 0, 0);
1654 
1655 	return (mouse->count >= 0);
1656 }
1657 
1658 // Mouse enters/leaves the canvas
canvas_enter_leave(main_dd * dt,void ** wdata,int what,void ** where,void * enter)1659 static void canvas_enter_leave(main_dd *dt, void **wdata, int what, void **where,
1660 	void *enter)
1661 {
1662 	if (enter)
1663 	{
1664 		mouse_left_canvas = FALSE;
1665 		return;
1666 	}
1667 	/* If leaving canvas */
1668 	/* Only do this if we have an image */
1669 	if (!mem_img[CHN_IMAGE]) return;
1670 	mouse_left_canvas = TRUE;
1671 	if (status_on[STATUS_CURSORXY])
1672 		cmd_setv(label_bar[STATUS_CURSORXY], "", LABEL_VALUE);
1673 	if (status_on[STATUS_PIXELRGB])
1674 		cmd_setv(label_bar[STATUS_PIXELRGB], "", LABEL_VALUE);
1675 	if (perim_status > 0) clear_perim(&perim_state);
1676 
1677 	clone_status &= ~CLONE_TRACK; // No tracking w/o perimeter
1678 
1679 	if (tool_type == TOOL_GRADIENT)
1680 	{
1681 		/* Let leave hide the dragged line */
1682 		grad_info *grad = gradient + mem_channel;
1683 		if ((grad->status == GRAD_START) || (grad->status == GRAD_END))
1684 			repaint_grad(NULL);
1685 	}
1686 
1687 	if (((tool_type == TOOL_POLYGON) && (poly_status == POLY_SELECTING)) ||
1688 		((tool_type == TOOL_LINE) && (line_status == LINE_LINE)))
1689 		repaint_line(NULL);
1690 
1691 	is_tracking = FALSE;
1692 }
1693 
render_background(unsigned char * rgb,int x0,int y0,int wid,int hgt,int fwid)1694 static int render_background(unsigned char *rgb, int x0, int y0, int wid, int hgt, int fwid)
1695 {
1696 	int i, j, k, scale, dx, dy, step, ii, jj, ii0, px, py;
1697 	int xwid = 0, xhgt = 0, wid3 = wid * 3;
1698 
1699 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1700 	if (!chequers_optimize) step = 8;
1701 	else if (can_zoom < 1.0) step = 6;
1702 	else
1703 	{
1704 		scale = rint(can_zoom);
1705 		step = scale < 4 ? 6 : scale == 4 ? 8 : scale;
1706 	}
1707 	dx = x0 % step;
1708 	dy = y0 % step;
1709 
1710 	py = (x0 / step + y0 / step) & 1;
1711 	if (hgt + dy > step)
1712 	{
1713 		jj = step - dy;
1714 		xhgt = (hgt + dy) % step;
1715 		if (!xhgt) xhgt = step;
1716 		hgt -= xhgt;
1717 		xhgt -= step;
1718 	}
1719 	else jj = hgt--;
1720 	if (wid + dx > step)
1721 	{
1722 		ii0 = step - dx;
1723 		xwid = (wid + dx) % step;
1724 		if (!xwid) xwid = step;
1725 		wid -= xwid;
1726 		xwid -= step;
1727 	}
1728 	else ii0 = wid--;
1729 
1730 	for (j = 0; ; jj += step)
1731 	{
1732 		if (j >= hgt)
1733 		{
1734 			if (j > hgt) break;
1735 			jj += xhgt;
1736 		}
1737 		px = py;
1738 		ii = ii0;
1739 		for (i = 0; ; ii += step)
1740 		{
1741 			if (i >= wid)
1742 			{
1743 				if (i > wid) break;
1744 				ii += xwid;
1745 			}
1746 			k = (ii - i) * 3;
1747 			memset(rgb, greyz[px], k);
1748 			rgb += k;
1749 			px ^= 1;
1750 			i = ii;
1751 		}
1752 		rgb += fwid - wid3;
1753 		for(j++; j < jj; j++)
1754 		{
1755 			memcpy(rgb, rgb - fwid, wid3);
1756 			rgb += fwid;
1757 		}
1758 		py ^= 1;
1759 	}
1760 
1761 	return (!chequers_optimize); // Request async_bk
1762 }
1763 
1764 /// TRACING IMAGE
1765 
1766 unsigned char *bkg_rgb;
1767 int bkg_x, bkg_y, bkg_w, bkg_h, bkg_scale, bkg_flag;
1768 
config_bkg(int src)1769 int config_bkg(int src)
1770 {
1771 	image_info *img;
1772 	int l;
1773 
1774 	if (!src) return (TRUE); // No change
1775 
1776 	// Remove old
1777 	free(bkg_rgb);
1778 	bkg_rgb = NULL;
1779 	bkg_w = bkg_h = 0;
1780 
1781 	img = src == 2 ? &mem_image : src == 3 ? &mem_clip : NULL;
1782 	if (!img || !img->img[CHN_IMAGE]) return (TRUE); // No image
1783 
1784 	l = img->width * img->height;
1785 	bkg_rgb = malloc(l * 3);
1786 	if (!bkg_rgb) return (FALSE);
1787 
1788 	if (img->bpp == 1)
1789 		do_convert_rgb(0, 1, l, bkg_rgb, img->img[CHN_IMAGE], mem_pal);
1790 	else memcpy(bkg_rgb, img->img[CHN_IMAGE], l * 3);
1791 	bkg_w = img->width;
1792 	bkg_h = img->height;
1793 	return (TRUE);
1794 }
1795 
render_bkg(rgbcontext * ctx)1796 static int render_bkg(rgbcontext *ctx)
1797 {
1798 	unsigned char *src, *dest;
1799 	int i, x0, x, y, ty, w3, l3, d0, dd, adj, bs, rxy[4];
1800 	int zoom = 1, scale = 1;
1801 
1802 
1803 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
1804 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
1805 	else scale = rint(can_zoom);
1806 
1807 	bs = bkg_scale * zoom;
1808 	adj = bs - scale > 0 ? bs - scale : 0;
1809 	if (!clip(rxy, floor_div(bkg_x * scale + adj, bs) + margin_main_x,
1810 		floor_div(bkg_y * scale + adj, bs) + margin_main_y,
1811 		floor_div((bkg_x + bkg_w) * scale + adj, bs) + margin_main_x,
1812 		floor_div((bkg_y + bkg_h) * scale + adj, bs) + margin_main_y,
1813 		ctx->xy)) return (FALSE);
1814 
1815 	w3 = (ctx->xy[2] - ctx->xy[0]) * 3;
1816 	dest = ctx->rgb + (rxy[1] - ctx->xy[1]) * w3 + (rxy[0] - ctx->xy[0]) * 3;
1817 	l3 = (rxy[2] - rxy[0]) * 3;
1818 
1819 	d0 = (rxy[0] - margin_main_x) * bs;
1820 	x0 = floor_div(d0, scale);
1821 	d0 -= x0 * scale;
1822 	x0 -= bkg_x;
1823 
1824 	for (ty = -1 , i = rxy[1]; i < rxy[3]; i++)
1825 	{
1826 		y = floor_div((i - margin_main_y) * bs, scale) - bkg_y;
1827 		if (y != ty)
1828 		{
1829 			src = bkg_rgb + (y * bkg_w + x0) * 3;
1830 			for (dd = d0 , x = rxy[0]; x < rxy[2]; x++ , dest += 3)
1831 			{
1832 				dest[0] = src[0];
1833 				dest[1] = src[1];
1834 				dest[2] = src[2];
1835 				for (dd += bs; dd >= scale; dd -= scale)
1836 					src += 3;
1837 			}
1838 			ty = y;
1839 			dest += w3 - l3;
1840 		}
1841 		else
1842 		{
1843 			memcpy(dest, dest - w3, l3);
1844 			dest += w3;
1845 		}
1846 	}
1847 
1848 	return (scale > 1); // Request async_bk
1849 }
1850 
get_bkg(int xc,int yc,int dclick)1851 static int get_bkg(int xc, int yc, int dclick)
1852 {
1853 	int xb, yb, xi, yi, x, scale;
1854 
1855 	/* No background / not RGB / wrong scale */
1856 	if (!bkg_flag || (mem_channel != CHN_IMAGE) || (mem_img_bpp != 3) ||
1857 		(can_zoom < 1.0)) return (-1);
1858 	scale = rint(can_zoom);
1859 	xi = floor_div(xc - margin_main_x, scale);
1860 	yi = floor_div(yc - margin_main_y, scale);
1861 	/* Inside image */
1862 	if ((xi >= 0) && (xi < mem_width) && (yi >= 0) && (yi < mem_height))
1863 	{
1864 		/* Pixel must be transparent */
1865 		x = mem_width * yi + xi;
1866 		if (mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA] &&
1867 			!mem_img[CHN_ALPHA][x]); // Alpha transparency
1868 		else if (mem_xpm_trans < 0) return (-1);
1869 		else if (x *= 3 , MEM_2_INT(mem_img[CHN_IMAGE], x) !=
1870 			PNG_2_INT(mem_pal[mem_xpm_trans])) return (-1);
1871 
1872 		/* Double click averages background under image pixel */
1873 		if (dclick)
1874 		{
1875 			int vxy[4];
1876 
1877 			vxy[2] = (vxy[0] = xi * bkg_scale - bkg_x) + bkg_scale;
1878 			vxy[3] = (vxy[1] = yi * bkg_scale - bkg_y) + bkg_scale;
1879 
1880 			return (clip(vxy, 0, 0, bkg_w, bkg_h, vxy) ?
1881 				average_pixels(bkg_rgb, NULL, bkg_w, vxy) : -1);
1882 		}
1883 	}
1884 	xb = floor_div((xc - margin_main_x) * bkg_scale, scale) - bkg_x;
1885 	yb = floor_div((yc - margin_main_y) * bkg_scale, scale) - bkg_y;
1886 	/* Outside of background */
1887 	if ((xb < 0) || (xb >= bkg_w) || (yb < 0) || (yb >= bkg_h)) return (-1);
1888 	x = (bkg_w * yb + xb) * 3;
1889 	return (MEM_2_INT(bkg_rgb, x));
1890 }
1891 
1892 /* This is set when background is at different scale than image */
1893 int async_bk;
1894 
setup_row(renderstate * r,int x0,int width,int zoom,int scale,int mw,int xpm,int opac,int bpp,png_color * pal)1895 void setup_row(renderstate *r, int x0, int width, int zoom, int scale, int mw,
1896 	int xpm, int opac, int bpp, png_color *pal)
1897 {
1898 	renderstate rr;
1899 
1900 	/* Horizontal zoom */
1901 	rr.zoom = zoom;
1902 	rr.scale = scale;
1903 	x0 %= rr.scale;
1904 	if (x0 < 0) x0 += rr.scale;
1905 
1906 	if (width + x0 > rr.scale)
1907 	{
1908 		rr.dx = rr.scale - x0;
1909 		x0 = (width + x0) % rr.scale;
1910 		if (!x0) x0 = rr.scale;
1911 		width -= x0;
1912 		rr.xwid = x0 - rr.scale;
1913 	}
1914 	else
1915 	{
1916 		rr.dx = width--;
1917 		rr.xwid = 0;
1918 	}
1919 	rr.width = width;
1920 	rr.mw = mw;
1921 
1922 	if ((xpm > -1) && (bpp == 3)) xpm = PNG_2_INT(pal[xpm]);
1923 	rr.xpm = xpm;
1924 	rr.opac = opac;
1925 
1926 	rr.bpp = bpp;
1927 	rr.pal = pal;
1928 	rr.cmask = 0;
1929 
1930 	*r = rr;
1931 }
1932 
render_row(renderstate * r,unsigned char * rgb,chanlist base_img,int x,int y,chanlist xtra_img)1933 void render_row(renderstate *r, unsigned char *rgb, chanlist base_img,
1934 	int x, int y, chanlist xtra_img)
1935 {
1936 	renderstate rr = *r;
1937 	int alpha_blend = !overlay_alpha;
1938 	unsigned char *src = NULL, *dest, *alpha = NULL, px, beta = 255;
1939 	int i, j, k, ii, ds = rr.zoom * 3, da = 0;
1940 	int w_bpp = rr.bpp, w_xpm = rr.xpm;
1941 
1942 	if (xtra_img)
1943 	{
1944 		src = xtra_img[CHN_IMAGE];
1945 		alpha = xtra_img[CHN_ALPHA];
1946 	}
1947 	if (rr.cmask & CMASK_ALPHA) alpha = &beta; /* Ignore alpha if disabled */
1948 	if (!src) src = base_img[CHN_IMAGE] + (rr.mw * y + x) * rr.bpp;
1949 	if (!alpha) alpha = base_img[CHN_ALPHA] ? base_img[CHN_ALPHA] +
1950 		rr.mw * y + x : &beta;
1951 	if (alpha != &beta) da = rr.zoom;
1952 	dest = rgb;
1953 	ii = rr.dx;
1954 
1955 	/* Substitute non-transparent "image overlay" colour */
1956 	if (rr.cmask & CMASK_IMAGE)
1957 	{
1958 		w_bpp = 3;
1959 		w_xpm = -1;
1960 		ds = 0;
1961 		src = channel_rgb[CHN_IMAGE];
1962 	}
1963 	if (!da && (w_xpm < 0) && (rr.opac == 255)) alpha_blend = FALSE;
1964 
1965 	/* Indexed fully opaque */
1966 	if ((w_bpp == 1) && !alpha_blend)
1967 	{
1968 		for (i = 0; ; ii += rr.scale)
1969 		{
1970 			if (i >= rr.width)
1971 			{
1972 				if (i > rr.width) break;
1973 				ii += rr.xwid;
1974 			}
1975 			px = *src;
1976 			src += rr.zoom;
1977 			for(; i < ii; i++)
1978 			{
1979 				dest[0] = rr.pal[px].red;
1980 				dest[1] = rr.pal[px].green;
1981 				dest[2] = rr.pal[px].blue;
1982 				dest += 3;
1983 			}
1984 		}
1985 	}
1986 
1987 	/* Indexed transparent */
1988 	else if (w_bpp == 1)
1989 	{
1990 		for (i = 0; ; ii += rr.scale , alpha += da)
1991 		{
1992 			if (i >= rr.width)
1993 			{
1994 				if (i > rr.width) break;
1995 				ii += rr.xwid;
1996 			}
1997 			px = *src;
1998 			src += rr.zoom;
1999 			if (!*alpha || (px == w_xpm))
2000 			{
2001 				dest += (ii - i) * 3;
2002 				i = ii;
2003 				continue;
2004 			}
2005 rr2_as:
2006 			if (rr.opac == 255)
2007 			{
2008 				dest[0] = rr.pal[px].red;
2009 				dest[1] = rr.pal[px].green;
2010 				dest[2] = rr.pal[px].blue;
2011 			}
2012 			else
2013 			{
2014 				j = 255 * dest[0] + rr.opac * (rr.pal[px].red - dest[0]);
2015 				dest[0] = (j + (j >> 8) + 1) >> 8;
2016 				j = 255 * dest[1] + rr.opac * (rr.pal[px].green - dest[1]);
2017 				dest[1] = (j + (j >> 8) + 1) >> 8;
2018 				j = 255 * dest[2] + rr.opac * (rr.pal[px].blue - dest[2]);
2019 				dest[2] = (j + (j >> 8) + 1) >> 8;
2020 			}
2021 rr2_s:
2022 			dest += 3;
2023 			if (++i >= ii) continue;
2024 			if (async_bk) goto rr2_as;
2025 			dest[0] = *(dest - 3);
2026 			dest[1] = *(dest - 2);
2027 			dest[2] = *(dest - 1);
2028 			goto rr2_s;
2029 		}
2030 	}
2031 
2032 	/* RGB fully opaque */
2033 	else if (!alpha_blend)
2034 	{
2035 		for (i = 0; ; ii += rr.scale , src += ds)
2036 		{
2037 			if (i >= rr.width)
2038 			{
2039 				if (i > rr.width) break;
2040 				ii += rr.xwid;
2041 			}
2042 			for(; i < ii; i++)
2043 			{
2044 				dest[0] = src[0];
2045 				dest[1] = src[1];
2046 				dest[2] = src[2];
2047 				dest += 3;
2048 			}
2049 		}
2050 	}
2051 
2052 	/* RGB transparent */
2053 	else
2054 	{
2055 		for (i = 0; ; ii += rr.scale , src += ds , alpha += da)
2056 		{
2057 			if (i >= rr.width)
2058 			{
2059 				if (i > rr.width) break;
2060 				ii += rr.xwid;
2061 			}
2062 			if (!*alpha || (MEM_2_INT(src, 0) == w_xpm))
2063 			{
2064 				dest += (ii - i) * 3;
2065 				i = ii;
2066 				continue;
2067 			}
2068 			k = rr.opac * alpha[0];
2069 			k = (k + (k >> 8) + 1) >> 8;
2070 rr4_as:
2071 			if (k == 255)
2072 			{
2073 				dest[0] = src[0];
2074 				dest[1] = src[1];
2075 				dest[2] = src[2];
2076 			}
2077 			else
2078 			{
2079 				j = 255 * dest[0] + k * (src[0] - dest[0]);
2080 				dest[0] = (j + (j >> 8) + 1) >> 8;
2081 				j = 255 * dest[1] + k * (src[1] - dest[1]);
2082 				dest[1] = (j + (j >> 8) + 1) >> 8;
2083 				j = 255 * dest[2] + k * (src[2] - dest[2]);
2084 				dest[2] = (j + (j >> 8) + 1) >> 8;
2085 			}
2086 rr4_s:
2087 			dest += 3;
2088 			if (++i >= ii) continue;
2089 			if (async_bk) goto rr4_as;
2090 			dest[0] = *(dest - 3);
2091 			dest[1] = *(dest - 2);
2092 			dest[2] = *(dest - 1);
2093 			goto rr4_s;
2094 		}
2095 	}
2096 }
2097 
overlay_row(renderstate * r,unsigned char * rgb,chanlist base_img,int x,int y,chanlist xtra_img)2098 void overlay_row(renderstate *r, unsigned char *rgb, chanlist base_img,
2099 	int x, int y, chanlist xtra_img)
2100 {
2101 	renderstate rr = *r;
2102 	unsigned char *alpha, *sel, *mask, *dest;
2103 	int i, j, k, ii, dw, opA, opS, opM, t0, t1, t2, t3;
2104 
2105 	if (xtra_img)
2106 	{
2107 		alpha = xtra_img[CHN_ALPHA];
2108 		sel = xtra_img[CHN_SEL];
2109 		mask = xtra_img[CHN_MASK];
2110 	}
2111 	else alpha = sel = mask = NULL;
2112 	j = rr.mw * y + x;
2113 	if (!alpha && base_img[CHN_ALPHA]) alpha = base_img[CHN_ALPHA] + j;
2114 	if (!sel && base_img[CHN_SEL]) sel = base_img[CHN_SEL] + j;
2115 	if (!mask && base_img[CHN_MASK]) mask = base_img[CHN_MASK] + j;
2116 
2117 	/* Prepare channel weights (256-based) */
2118 	k = rr.cmask & CMASK_IMAGE ? 256 : 256 - channel_opacity[CHN_IMAGE] -
2119 		(channel_opacity[CHN_IMAGE] >> 7);
2120 	opA = alpha && overlay_alpha && !(rr.cmask & CMASK_ALPHA) ?
2121 		channel_opacity[CHN_ALPHA] : 0;
2122 	opS = sel && !(rr.cmask & CMASK_SEL) ? channel_opacity[CHN_SEL] : 0;
2123 	opM = mask && !(rr.cmask & CMASK_MASK) ? channel_opacity[CHN_MASK] : 0;
2124 
2125 	/* Nothing to do - don't waste time then */
2126 	j = opA + opS + opM;
2127 	if (!k || !j) return;
2128 
2129 	opA = (k * opA) / j;
2130 	opS = (k * opS) / j;
2131 	opM = (k * opM) / j;
2132 	if (!(opA + opS + opM)) return;
2133 
2134 	dest = rgb;
2135 	ii = rr.dx;
2136 	for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
2137 	{
2138 		if (i >= rr.width)
2139 		{
2140 			if (i > rr.width) break;
2141 			ii += rr.xwid;
2142 		}
2143 		t0 = t1 = t2 = t3 = 0;
2144 		if (opA)
2145 		{
2146 			j = opA * (alpha[dw] ^ channel_inv[CHN_ALPHA]);
2147 			t0 += j;
2148 			t1 += j * channel_rgb[CHN_ALPHA][0];
2149 			t2 += j * channel_rgb[CHN_ALPHA][1];
2150 			t3 += j * channel_rgb[CHN_ALPHA][2];
2151 		}
2152 		if (opS)
2153 		{
2154 			j = opS * (sel[dw] ^ channel_inv[CHN_SEL]);
2155 			t0 += j;
2156 			t1 += j * channel_rgb[CHN_SEL][0];
2157 			t2 += j * channel_rgb[CHN_SEL][1];
2158 			t3 += j * channel_rgb[CHN_SEL][2];
2159 		}
2160 		if (opM)
2161 		{
2162 			j = opM * (mask[dw] ^ channel_inv[CHN_MASK]);
2163 			t0 += j;
2164 			t1 += j * channel_rgb[CHN_MASK][0];
2165 			t2 += j * channel_rgb[CHN_MASK][1];
2166 			t3 += j * channel_rgb[CHN_MASK][2];
2167 		}
2168 		j = (256 * 255) - t0;
2169 or_as:
2170 		k = t1 + j * dest[0];
2171 		dest[0] = (k + (k >> 8) + 0x100) >> 16;
2172 		k = t2 + j * dest[1];
2173 		dest[1] = (k + (k >> 8) + 0x100) >> 16;
2174 		k = t3 + j * dest[2];
2175 		dest[2] = (k + (k >> 8) + 0x100) >> 16;
2176 or_s:
2177 		dest += 3;
2178 		if (++i >= ii) continue;
2179 		if (async_bk) goto or_as;
2180 		dest[0] = *(dest - 3);
2181 		dest[1] = *(dest - 2);
2182 		dest[2] = *(dest - 1);
2183 		goto or_s;
2184 	}
2185 }
2186 
2187 /* Specialized renderer for irregular overlays */
overlay_preview(renderstate * r,unsigned char * rgb,unsigned char * map,int col,int opacity)2188 void overlay_preview(renderstate *r, unsigned char *rgb, unsigned char *map,
2189 	int col, int opacity)
2190 {
2191 	renderstate rr = *r;
2192 	unsigned char *dest, crgb[3] = {INT_2_R(col), INT_2_G(col), INT_2_B(col)};
2193 	int i, j, k, ii, dw;
2194 
2195 	dest = rgb;
2196 	ii = rr.dx;
2197 	for (i = dw = 0; ; ii += rr.scale , dw += rr.zoom)
2198 	{
2199 		if (i >= rr.width)
2200 		{
2201 			if (i > rr.width) break;
2202 			ii += rr.xwid;
2203 		}
2204 		k = opacity * map[dw];
2205 		k = (k + (k >> 8) + 1) >> 8;
2206 op_as:
2207 		j = 255 * dest[0] + k * (crgb[0] - dest[0]);
2208 		dest[0] = (j + (j >> 8) + 1) >> 8;
2209 		j = 255 * dest[1] + k * (crgb[1] - dest[1]);
2210 		dest[1] = (j + (j >> 8) + 1) >> 8;
2211 		j = 255 * dest[2] + k * (crgb[2] - dest[2]);
2212 		dest[2] = (j + (j >> 8) + 1) >> 8;
2213 op_s:
2214 		dest += 3;
2215 		if (++i >= ii) continue;
2216 		if (async_bk) goto op_as;
2217 		dest[0] = *(dest - 3);
2218 		dest[1] = *(dest - 2);
2219 		dest[2] = *(dest - 1);
2220 		goto op_s;
2221 	}
2222 }
2223 
2224 typedef struct {
2225 	int rgb_s;		// For when need RGB with 1bpp channel
2226 	int mask_s;		// For most everything
2227 	int overlay_s;		// For extra overlay
2228 	int channel_s;		// For composited channel
2229 	int alpha_s;		// For composited alpha
2230 	int n_channel_s;	// For generated/transformed channel
2231 	int n_alpha_s;		// For generated alpha
2232 	int n_opacity_s;	// For generated opacity
2233 	unsigned char *rgb,
2234 		*mask,
2235 		*overlay,
2236 		*channel,
2237 		*alpha,
2238 		*n_channel,
2239 		*n_alpha,
2240 		*n_opacity;	// Pointers to same
2241 } render_mem_req;
2242 
2243 typedef struct {
2244 	unsigned char *wmask, *gmask, *walpha, *galpha;
2245 	unsigned char *wimg, *gimg, *rgb;
2246 	int opac, len, bpp;
2247 } grad_render_state;
2248 
grad_render_req(render_mem_req * mr,int len)2249 static int grad_render_req(render_mem_req *mr, int len)
2250 {
2251 	int bpp;
2252 
2253 // !!! Only the "slow path" for now
2254 	if (gradient[mem_channel].status != GRAD_DONE) return (FALSE);
2255 
2256 	bpp = MEM_BPP;
2257 	mr->mask_s = mr->n_opacity_s = len;
2258 	mr->channel_s = mr->n_channel_s = len * bpp;
2259 	if ((mem_channel == CHN_IMAGE) && RGBA_mode && mem_img[CHN_ALPHA])
2260 		mr->alpha_s = mr->n_alpha_s = len;
2261 	if (IS_INDEXED && (grad_opacity < 255)) mr->rgb_s = len * 3;
2262 	return (TRUE);
2263 }
2264 
init_grad_render(render_mem_req * mr,grad_render_state * g,int len,chanlist tlist)2265 static void init_grad_render(render_mem_req *mr, grad_render_state *g, int len,
2266 	chanlist tlist)
2267 {
2268 	memset(g, 0, sizeof(grad_render_state));
2269 
2270 	g->wmask = mr->mask;		/* Mask */
2271 	g->gmask = mr->n_opacity;	/* Gradient opacity */
2272 	g->gimg = mr->n_channel;	/* Gradient image */
2273 	g->wimg = mr->channel;		/* Resulting image */
2274 	g->galpha = mr->n_alpha;	/* Gradient alpha */
2275 	g->walpha = mr->alpha;		/* Resulting alpha */
2276 	g->rgb = mr->rgb;		/* Indexed to RGB */
2277 
2278 	tlist[CHN_ALPHA] = g->walpha;
2279 	tlist[mem_channel] = g->wimg;
2280 	if (g->rgb) tlist[CHN_IMAGE] = g->rgb;
2281 	g->opac = IS_INDEXED ? 0 : grad_opacity;
2282 	g->len = len;
2283 	g->bpp = MEM_BPP;
2284 }
2285 
grad_render(int start,int step,int cnt,int x,int y,unsigned char * mask0,grad_render_state * g)2286 static void grad_render(int start, int step, int cnt, int x, int y,
2287 	unsigned char *mask0, grad_render_state *g)
2288 {
2289 	int l = mem_width * y + x, li = l * mem_img_bpp;
2290 
2291 	prep_mask(start, step, cnt, g->wmask, mask0, mem_img[CHN_IMAGE] + li);
2292 
2293 	grad_pixels(start, step, cnt, x, y, g->wmask, g->gmask, g->gimg, g->galpha);
2294 
2295 	if (g->walpha) memcpy(g->walpha, mem_img[CHN_ALPHA] + l, g->len);
2296 	process_mask(start, step, cnt, g->wmask, g->walpha, mem_img[CHN_ALPHA] + l,
2297 		g->galpha, g->gmask, g->opac, channel_dis[CHN_ALPHA]);
2298 
2299 	memcpy(g->wimg, mem_img[mem_channel] + l * g->bpp, g->len * g->bpp);
2300 	process_img(start, step, cnt, g->wmask, g->wimg, g->wimg, g->gimg,
2301 		g->gimg, g->bpp, 0);
2302 
2303 	if (g->rgb) blend_indexed(start, step, cnt, g->rgb, mem_img[CHN_IMAGE] + l,
2304 		g->wimg, mem_img[CHN_ALPHA] + l, g->walpha, grad_opacity);
2305 }
2306 
2307 typedef struct {
2308 	chanlist tlist;		// Channel overrides
2309 	unsigned char *mask0;	// Active mask channel
2310 	unsigned char *pvi;	// Xform/xhold render: temp image row
2311 	unsigned char *pvm;	// Xform/xhold render: temp mask row
2312 	int rxy[4];		// Clipped area
2313 	int dx;			// Image-space X offset
2314 	int lx;			// Allocated row length
2315 	int pww;		// Logical row length
2316 	int zoom;		// Decimation factor
2317 	int scale;		// Replication factor
2318 	int lop;		// Base opacity
2319 	int xpm;		// Transparent color
2320 } main_render_state;
2321 
2322 typedef struct {
2323 	chanlist tlist;		// Channel overrides for rendering clipboard
2324 	unsigned char *clip_alpha;	// Pasted into alpha channel
2325 	unsigned char *t_alpha;		// Fake pasted alpha
2326 	unsigned char *pix, *alpha;	// Destinations for the above
2327 	unsigned char *mask, *wmask;	// Temp mask: one we use, other we init
2328 	unsigned char *mask0;		// Image mask channel to use
2329 	unsigned char *xbuf;	// Extra buffer for process_img()
2330 	int opacity, bpp;	// Just that
2331 	int pixf;		// Flag: need current channel override filled
2332 	int dx;			// Memory-space X offset
2333 	int lx;			// Allocated row length
2334 	int pww;		// Logical row length
2335 	int xform;		// Flag: do color transform
2336 	int maskf;		// Flag: need mask filled
2337 } paste_render_state;
2338 
2339 /* !!! If ever combined with more than color transform, must not unconditionally
2340  * !!! assign potentially shorter (paste area sized) lengths to buffers */
paste_render_req(render_mem_req * mr,paste_render_state * p,main_render_state * r)2341 static int paste_render_req(render_mem_req *mr, paste_render_state *p,
2342 	main_render_state *r)
2343 {
2344 	int rxy[4], bpp, scale = r->scale, zoom = r->zoom;
2345 
2346 
2347 	/* Clip paste area to update area */
2348 	if (!clip(rxy, (marq_x1 * scale + zoom - 1) / zoom,
2349 		(marq_y1 * scale + zoom - 1) / zoom,
2350 		(marq_x2 * scale) / zoom + scale,
2351 		(marq_y2 * scale) / zoom + scale, r->rxy)) return (FALSE);
2352 
2353 	/* Setup row position and size */
2354 	p->dx = (rxy[0] * zoom) / scale;
2355 	p->pww = xy_span(rxy, scale, 0);
2356 	p->lx = (p->pww - 1) * zoom + 1;
2357 
2358 	if ((mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA] &&
2359 		!channel_dis[CHN_ALPHA])
2360 	{
2361 		// Need temp alpha
2362 		if (mem_clip_alpha || RGBA_mode) mr->alpha_s = r->lx;
2363 		// Need fake alpha
2364 		if (!mem_clip_alpha && RGBA_mode) mr->n_alpha_s = p->lx;
2365 	}
2366 
2367 	bpp = p->bpp = MEM_BPP;
2368 	// Need temp mask
2369 	if ((p->maskf = !mr->mask_s)) mr->mask_s = p->lx;
2370 	// Need temp image
2371 	p->pixf = !mr->channel_s; /* Need it prefilled if no override data incoming */
2372 	mr->channel_s = r->lx * bpp;
2373 	// Need xform buffer
2374 	p->xform = mem_preview_clip && (bpp == 3) && (mem_clip_bpp == 3);
2375 	if (p->xform || NEED_XBUF_PASTE) mr->n_channel_s = p->lx * bpp;
2376 
2377 	/* Setup opacity mode */
2378 	if (!IS_INDEXED) p->opacity = tool_opacity;
2379 
2380 	return (TRUE);
2381 }
2382 
2383 /* !!! This function copies existing override set to build its own modified
2384  * !!! one, so override set must not be changed after calling it */
init_paste_render(render_mem_req * mr,paste_render_state * p,main_render_state * r)2385 static void init_paste_render(render_mem_req *mr, paste_render_state *p,
2386 	main_render_state *r)
2387 {
2388 	int ddx = p->dx - r->dx;
2389 
2390 
2391 	memcpy(p->tlist, r->tlist, sizeof(chanlist));
2392 
2393 	if ((mem_channel == CHN_IMAGE) && !channel_dis[CHN_ALPHA])
2394 		p->clip_alpha = mem_clip_alpha;
2395 
2396 	p->tlist[CHN_ALPHA] = mr->alpha;
2397 	p->tlist[mem_channel] = mr->channel;
2398 	p->mask = mr->mask;
2399 	p->t_alpha = mr->n_alpha;
2400 	p->xbuf = mr->n_channel;
2401 
2402 	/* Setup "image" (current) channel override */
2403 	p->pix = p->tlist[mem_channel] + ddx * p->bpp;
2404 
2405 	/* Setup alpha channel override */
2406 	if (mr->alpha) p->alpha = p->tlist[CHN_ALPHA] + ddx;
2407 
2408 	/* Setup mask */
2409 	if (mem_channel <= CHN_ALPHA) p->mask0 = r->mask0;
2410 	if (p->maskf) p->wmask = p->mask;
2411 	else
2412 	{
2413 		p->mask += ddx;
2414 		if (r->mask0 != p->mask0)
2415 		/* Mask has wrong data - reuse memory but refill values */
2416 			p->wmask = p->mask;
2417 	}
2418 
2419 	/* Setup fake alpha */
2420 	if (p->t_alpha) memset(p->t_alpha, channel_col_A[CHN_ALPHA], p->lx);
2421 }
2422 
paste_render(int start,int step,int y,paste_render_state * p)2423 static void paste_render(int start, int step, int y, paste_render_state *p)
2424 {
2425 	int ld = mem_width * y + p->dx;
2426 	int dc = mem_clip_w * (y - marq_y1) + p->dx - marq_x1;
2427 	int bpp = p->bpp;
2428 	int cnt = p->pww;
2429 	unsigned char *clip_src = mem_clipboard + dc * mem_clip_bpp;
2430 
2431 	if (p->wmask) prep_mask(start, step, cnt, p->wmask, p->mask0 ?
2432 		p->mask0 + ld : NULL, mem_img[CHN_IMAGE] + ld * mem_img_bpp);
2433 	process_mask(start, step, cnt, p->mask, p->alpha, mem_img[CHN_ALPHA] + ld,
2434 		p->clip_alpha ? p->clip_alpha + dc : p->t_alpha,
2435 		mem_clip_mask ? mem_clip_mask + dc : NULL, p->opacity, 0);
2436 	if (p->xform) /* Apply color transform if preview requested */
2437 	{
2438 		do_transform(start, step, cnt, p->mask, p->xbuf, clip_src, 0);
2439 		clip_src = p->xbuf;
2440 	}
2441 	if (mem_clip_bpp < bpp)
2442 	{
2443 		/* Convert paletted clipboard to RGB */
2444 		do_convert_rgb(start, step, cnt, p->xbuf, clip_src,
2445 			mem_clip_paletted ? mem_clip_pal : mem_pal);
2446 		clip_src = p->xbuf;
2447 	}
2448 	process_img(start, step, cnt, p->mask, p->pix, p->pix, clip_src,
2449 		p->xbuf, bpp, 0);
2450 }
2451 
2452 typedef struct {
2453 	main_render_state r;
2454 	paste_render_state p;
2455 	render_mem_req m;
2456 	int tflag, xflag, gflag, pflag, lr;
2457 	int pw;
2458 	int cxy[4];
2459 	unsigned char *rgb, *irgb;
2460 	threaddata *tdata; // For simplicity
2461 } u_render_state;
2462 
2463 /* Filter preview modes */
2464 #define XF_NONE  0
2465 #define XF_XHOLD 1
2466 #define XF_NOISE 2
2467 
main_render_req(u_render_state * u)2468 static void main_render_req(u_render_state *u)
2469 {
2470 	main_render_state r = u->r;
2471 
2472 
2473 	if (!channel_dis[CHN_MASK]) r.mask0 = mem_img[CHN_MASK];
2474 
2475 	r.xpm = mem_xpm_trans;
2476 	r.lop = 255;
2477 	if (u->lr && layer_selected)
2478 		r.lop = (layer_table_p[layer_selected].opacity * 255 + 50) / 100;
2479 
2480 	/* Setup row position and size */
2481 	r.dx = (r.rxy[0] * r.zoom) / r.scale;
2482 	r.pww = xy_span(r.rxy, r.scale, 0);
2483 	r.lx = (r.pww - 1) * r.zoom + 1;
2484 
2485 	/* ****** Memory request phase ****** */
2486 
2487 	/* Color transform preview */
2488 	if ((u->tflag = mem_preview && (mem_img_bpp == 3)))
2489 	{
2490 		u->m.mask_s = r.lx;
2491 		if (mem_channel == CHN_IMAGE) u->m.channel_s = r.lx * 3;
2492 		else u->m.rgb_s = r.lx * 3;
2493 	}
2494 
2495 	/* Filter preview */
2496 	else if ((u->xflag = xhold_preview ? XF_XHOLD : noise_preview ? XF_NOISE : 0))
2497 	{
2498 		u->m.mask_s = r.lx;
2499 		u->m.channel_s = r.lx * MEM_BPP;
2500 	}
2501 
2502 	/* Color selective mode preview */
2503 	else if (csel_overlay) u->m.overlay_s = r.lx;
2504 
2505 	/* Gradient preview */
2506 	else if ((tool_type == TOOL_GRADIENT) && grad_opacity)
2507 		u->gflag = grad_render_req(&u->m, r.lx);
2508 
2509 	/* Paste preview - can only coexist with transform */
2510 	if (show_paste && (marq_status >= MARQUEE_PASTE) &&
2511 		!u->xflag && !u->m.overlay_s && !u->gflag)
2512 		u->pflag = paste_render_req(&u->m, &u->p, &r);
2513 
2514 	/* Pass the data */
2515 	u->r = r;
2516 }
2517 
main_render(u_render_state * u,int py,int ph)2518 static void main_render(u_render_state *u, int py, int ph)
2519 {
2520 	main_render_state r = u->r;
2521 	grad_render_state grstate;
2522 	renderstate rs;
2523 	unsigned char *rgb, **tlist = r.tlist, *overlay = u->m.overlay;
2524 	int j, jj, j0, l, pw2, pw;
2525 
2526 	/* ****** Init phase ****** */
2527 
2528 	/* Color transform preview */
2529 	if (u->tflag)
2530 	{
2531 		r.pvm = u->m.mask;
2532 		r.tlist[CHN_IMAGE] = r.pvi = u->m.rgb ? u->m.rgb : u->m.channel;
2533 	}
2534 
2535 	/* Threshold preview */
2536 	if (u->xflag)
2537 	{
2538 		if (mem_channel > CHN_ALPHA) r.mask0 = NULL;
2539 		r.pvm = u->m.mask;
2540 		r.tlist[mem_channel] = r.pvi = u->m.channel;
2541 	}
2542 
2543 	/* Gradient preview */
2544 	if (u->gflag)
2545 	{
2546 		if (mem_channel > CHN_ALPHA) r.mask0 = NULL;
2547 		init_grad_render(&u->m, &grstate, r.lx, r.tlist);
2548 	}
2549 
2550 	/* Paste preview */
2551 	if (u->pflag) init_paste_render(&u->m, &u->p, &r);
2552 
2553 	/* Start rendering */
2554 	pw2 = r.rxy[2] - r.rxy[0];
2555 	setup_row(&rs, r.rxy[0], pw2, r.zoom, r.scale, mem_width, r.xpm, r.lop,
2556 		u->gflag && grstate.rgb ? 3 : mem_img_bpp, mem_pal);
2557 	rs.cmask = (hide_image ? CMASK_IMAGE : 0) |
2558 		(channel_dis[CHN_ALPHA] ? CMASK_ALPHA : 0) |
2559 		(channel_dis[CHN_SEL] ? CMASK_SEL : 0) |
2560 		(channel_dis[CHN_MASK] ? CMASK_MASK : 0);
2561  	j0 = -1; pw2 *= 3;
2562 	rgb = u->irgb + (py - r.rxy[1]) * (pw = u->pw);
2563 	for (jj = 0; jj < ph; jj++ , rgb += pw)
2564 	{
2565 		j = ((py + jj) * r.zoom) / r.scale;
2566 		if (j != j0)
2567 		{
2568 			j0 = j;
2569 			l = mem_width * j + r.dx;
2570 			tlist = r.tlist; /* Default override */
2571 
2572 			/* Color transform/threshold preview */
2573 			if (u->tflag | u->xflag)
2574 			{
2575 				unsigned char *src, *img;
2576 				int bpp = mem_img_bpp;
2577 
2578 				src = img = mem_img[CHN_IMAGE] + l * bpp;
2579 
2580 				prep_mask(0, r.zoom, r.pww, r.pvm,
2581 					r.mask0 ? r.mask0 + l : NULL, img);
2582 				if (u->tflag) do_transform(0, r.zoom, r.pww,
2583 					r.pvm, r.pvi, src, 255);
2584 				else if (u->xflag == XF_XHOLD)
2585 				{
2586 					bpp = MEM_BPP;
2587 					src = mem_img[mem_channel] + l * bpp;
2588 					do_xhold(0, r.zoom, r.pww, r.pvm, r.pvi, src);
2589 				}
2590 				else /* if (u->xflag == XF_NOISE) */
2591 					do_perlin(0, r.zoom, r.pww, r.pvm, r.pvi, r.dx, j);
2592 				process_img(0, r.zoom, r.pww, r.pvm,
2593 					r.pvi, r.pvi, src, NULL, bpp, BLENDF_SET);
2594 			}
2595 			/* Color selective mode preview */
2596 			else if (overlay)
2597 			{
2598 				memset(overlay, 0, r.lx);
2599 				csel_scan(0, r.zoom, r.pww, overlay,
2600 					mem_img[CHN_IMAGE] + l * mem_img_bpp,
2601 					csel_data);
2602 			}
2603 			/* Gradient preview */
2604 			else if (u->gflag) grad_render(0, r.zoom, r.pww, r.dx, j,
2605 				r.mask0 ? r.mask0 + l : NULL, &grstate);
2606 
2607 			/* Paste preview - should be after transform */
2608 			if (u->pflag && (j >= marq_y1) && (j <= marq_y2))
2609 			{
2610 				tlist = u->p.tlist; /* Paste-area override */
2611 				if (u->p.alpha) memcpy(tlist[CHN_ALPHA],
2612 					mem_img[CHN_ALPHA] + l, r.lx);
2613 				if (u->p.pixf) memcpy(tlist[mem_channel],
2614 					mem_img[mem_channel] + l * u->p.bpp,
2615 					r.lx * u->p.bpp);
2616 				paste_render(0, r.zoom, j, &u->p);
2617 			}
2618 		}
2619 		else if (!async_bk)
2620 		{
2621 			memcpy(rgb, rgb - pw, pw2);
2622 			continue;
2623 		}
2624 		render_row(&rs, rgb, mem_img, r.dx, j, tlist);
2625 		if (!overlay) overlay_row(&rs, rgb, mem_img, r.dx, j, tlist);
2626 		else overlay_preview(&rs, rgb, overlay, csel_preview, csel_preview_a);
2627 	}
2628 }
2629 
2630 /// GRID
2631 
2632 int grid_rgb[GRID_MAX];	// Grid colors to use
2633 int mem_show_grid;	// Boolean show toggle
2634 int mem_grid_min;	// Minimum zoom to show it at
2635 int color_grid;		// If to use grid coloring
2636 int show_tile_grid;	// Tile grid toggle
2637 int tgrid_x0, tgrid_y0;	// Tile grid origin
2638 int tgrid_dx, tgrid_dy;	// Tile grid spacing
2639 int tgrid_snap;		// Coordinates snap toggle
2640 
2641 /* Snap coordinate pair to tile grid (floored) */
snap_xy(int * xy)2642 void snap_xy(int *xy)
2643 {
2644 	int dx, dy;
2645 
2646 	dx = (xy[0] - tgrid_x0) % tgrid_dx;
2647 	xy[0] -= dx + (dx < 0 ? tgrid_dx : 0);
2648 	dy = (xy[1] - tgrid_y0) % tgrid_dy;
2649 	xy[1] -= dy + (dy < 0 ? tgrid_dy : 0);
2650 }
2651 
2652 /* Buffer stores interleaved transparency bits for two pixel rows; current
2653  * row in bits 0, 2, etc., previous one in bits 1, 3, etc. */
scan_trans(unsigned char * dest,int delta,int y,int x,int w)2654 static void scan_trans(unsigned char *dest, int delta, int y, int x, int w)
2655 {
2656 	static unsigned char beta = 255;
2657 	unsigned char *src, *srca = &beta;
2658 	int i, ofs, bit, buf, xpm, da = 0, bpp = mem_img_bpp;
2659 
2660 
2661 	delta += delta; dest += delta >> 3; delta &= 7;
2662 	if (y >= mem_height) // Clear
2663 	{
2664 		for (i = 0; i < w; i++ , dest++) *dest = (*dest & 0x55) << 1;
2665 		return;
2666 	}
2667 
2668 	xpm = mem_xpm_trans < 0 ? -1 : bpp == 1 ? mem_xpm_trans :
2669 		PNG_2_INT(mem_pal[mem_xpm_trans]);
2670 	ofs = y * mem_width + x;
2671 	src = mem_img[CHN_IMAGE] + ofs * bpp;
2672 	if (mem_img[CHN_ALPHA]) srca = mem_img[CHN_ALPHA] + ofs , da = 1;
2673 	bit = 1 << delta;
2674 	buf = (*dest & 0x55) << 1;
2675 	for (i = 0; i < w; i++ , src += bpp , srca += da)
2676 	{
2677 		if (*srca && ((bpp == 1 ? *src : MEM_2_INT(src, 0)) != xpm))
2678 			buf |= bit;
2679 		if ((bit <<= 2) < 0x100) continue;
2680 		*dest++ = buf; buf = (*dest & 0x55) << 1; bit = 1;
2681 	}
2682 	*dest = buf;
2683 }
2684 
2685 /* Draw grid on rgb memory */
draw_grid(unsigned char * rgb,int x,int y,int w,int h,int l)2686 static void draw_grid(unsigned char *rgb, int x, int y, int w, int h, int l)
2687 {
2688 /* !!! This limit IN THEORY can be violated by a sufficiently huge screen, if
2689  * clipping to image isn't enforced in some fashion; this code can be made to
2690  * detect the violation and do the clipping (on X axis) for itself - really
2691  * colored is only the image proper + 1 pixel to bottom & right of it */
2692 	unsigned char lbuf[(MAX_WIDTH * 2 + 2 + 7) / 8 + 2];
2693 	int dx, dy, step = can_zoom;
2694 	int i, j, k, yy, wx, ww, x0, xw;
2695 
2696 
2697 	l *= 3;
2698 	dx = (x - margin_main_x) % step;
2699 	if (dx <= 0) dx += step;
2700 	dy = (y - margin_main_y) % step;
2701 	if (dy <= 0) dy += step;
2702 
2703 	/* Use dumb code for uncolored grid */
2704 	if (!color_grid || (x + w <= margin_main_x) || (y + h <= margin_main_y) ||
2705 		(x > margin_main_x + mem_width * step) ||
2706 		(y > margin_main_y + mem_height * step))
2707 	{
2708 		unsigned char *tmp;
2709 		int i, j, k, step3, tc;
2710 
2711 		tc = grid_rgb[color_grid ? GRID_TRANS : GRID_NORMAL];
2712 		dx = (step - dx) * 3;
2713 		w *= 3;
2714 
2715 		for (k = dy , i = 0; i < h; i++ , k++)
2716 		{
2717 			tmp = rgb + i * l;
2718 			if (k == step) /* Filled line */
2719 			{
2720 				j = k = 0; step3 = 3;
2721 			}
2722 			else /* Spaced dots */
2723 			{
2724 				j = dx; step3 = step * 3;
2725 			}
2726 			for (tmp += j; j < w; j += step3 , tmp += step3)
2727 			{
2728 				tmp[0] = INT_2_R(tc);
2729 				tmp[1] = INT_2_G(tc);
2730 				tmp[2] = INT_2_B(tc);
2731 			}
2732 		}
2733 		return;
2734 	}
2735 
2736 	wx = floor_div(x - margin_main_x - 1, step);
2737 	ww = (w + dx + step - 1) / step + 1;
2738 	memset(lbuf, 0, (ww + ww + 7 + 16) >> 3); // Init to transparent
2739 
2740 	x0 = wx < 0 ? 0 : wx;
2741 	xw = (wx + ww < mem_width ? wx + ww : mem_width) - x0;
2742 	wx = x0 - wx;
2743 	yy = floor_div(y - margin_main_y, step);
2744 
2745 	/* Initial row fill */
2746 	if (dy == step) yy--;
2747 	if ((yy >= 0) && (yy < mem_height)) // Else it stays cleared
2748 		scan_trans(lbuf, wx, yy, x0, xw);
2749 
2750 	for (k = dy , i = 0; i < h; i++ , k++)
2751 	{
2752 		unsigned char *tmp = rgb + i * l, *buf = lbuf + 2;
2753 
2754 		// Horizontal span
2755 		if (k == step)
2756 		{
2757 			int nv, tc, kk;
2758 
2759 			yy++; k = 0;
2760 			// Fill one more row
2761 			if ((yy >= 0) && (yy <= mem_height + 1))
2762 				scan_trans(lbuf, wx, yy, x0, xw);
2763 			nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
2764 			tc = grid_rgb[((nv & 3) + 1) >> 1];
2765 			for (kk = dx , j = 0; j < w; j++ , kk++ , tmp += 3)
2766 			{
2767 				if (kk != step) // Span
2768 				{
2769 					tmp[0] = INT_2_R(tc);
2770 					tmp[1] = INT_2_G(tc);
2771 					tmp[2] = INT_2_B(tc);
2772 					continue;
2773 				}
2774 				// Intersection
2775 				/* 0->0, 15->2, in-between remains between */
2776 				tc = grid_rgb[((nv & 0xF) * 9 + 0x79) >> 7];
2777 				tmp[0] = INT_2_R(tc);
2778 				tmp[1] = INT_2_G(tc);
2779 				tmp[2] = INT_2_B(tc);
2780 				nv >>= 2;
2781 				if (nv < 0x400)
2782 					nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
2783 				tc = grid_rgb[((nv & 3) + 1) >> 1];
2784 				kk = 0;
2785 			}
2786 		}
2787 		// Vertical spans
2788 		else
2789 		{
2790 			int nv = (lbuf[0] + (lbuf[1] << 8)) ^ 0x2FFFF; // Invert
2791 			j = step - dx; tmp += j * 3;
2792 			for (; j < w; j += step , tmp += step * 3)
2793 			{
2794 				/* 0->0, 5->2, in-between remains between */
2795 				int tc = grid_rgb[((nv & 5) + 3) >> 2];
2796 				tmp[0] = INT_2_R(tc);
2797 				tmp[1] = INT_2_G(tc);
2798 				tmp[2] = INT_2_B(tc);
2799 				nv >>= 2;
2800 				if (nv < 0x400)
2801 					nv ^= (*buf++ << 8) ^ 0x2FD00; // Invert
2802 			}
2803 		}
2804 	}
2805 }
2806 
2807 /* Draw tile grid on rgb memory */
draw_tgrid(unsigned char * rgb,int x,int y,int w,int h,int l)2808 static void draw_tgrid(unsigned char *rgb, int x, int y, int w, int h, int l)
2809 {
2810 	unsigned char *tmp, *tm2;
2811 	int i, j, k, dx, dy, nx, ny, xx, yy, tc, zoom = 1, scale = 1;
2812 
2813 
2814 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
2815 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
2816 	else scale = rint(can_zoom);
2817 	if ((tgrid_dx < zoom * 2) || (tgrid_dy < zoom * 2)) return; // Too dense
2818 
2819 	dx = tgrid_x0 ? tgrid_dx - tgrid_x0 : 0;
2820 	nx = (x * zoom - dx) / (tgrid_dx * scale);
2821 	if (nx < 0) nx = 0; nx++; dx++;
2822 	dy = tgrid_y0 ? tgrid_dy - tgrid_y0 : 0;
2823 	ny = (y * zoom - dy) / (tgrid_dy * scale);
2824 	if (ny < 0) ny = 0; ny++; dy++;
2825 	xx = ((nx * tgrid_dx - dx) * scale) / zoom + scale - 1;
2826 	yy = ((ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
2827 	if ((xx >= x + w) && (yy >= y + h)) return; // Entirely inside grid cell
2828 
2829 	l *= 3; tc = grid_rgb[GRID_TILE];
2830 	for (i = 0; i < h; i++)
2831 	{
2832 		tmp = rgb + l * i;
2833 		if (y + i == yy) /* Filled line */
2834 		{
2835 			for (j = 0; j < w; j++ , tmp += 3)
2836 			{
2837 				tmp[0] = INT_2_R(tc);
2838 				tmp[1] = INT_2_G(tc);
2839 				tmp[2] = INT_2_B(tc);
2840 			}
2841 			yy = ((++ny * tgrid_dy - dy) * scale) / zoom + scale - 1;
2842 			continue;
2843 		}
2844 		/* Spaced dots */
2845 		for (k = xx , j = nx + 1; k < x + w; j++)
2846 		{
2847 			tm2 = tmp + (k - x) * 3;
2848 			tm2[0] = INT_2_R(tc);
2849 			tm2[1] = INT_2_G(tc);
2850 			tm2[2] = INT_2_B(tc);
2851 			k = ((j * tgrid_dx - dx) * scale) / zoom + scale - 1;
2852 		}
2853 	}
2854 }
2855 
2856 /* Draw segmentation contours on rgb memory */
draw_segments(unsigned char * rgb,int x,int y,int w,int h,int l)2857 static void draw_segments(unsigned char *rgb, int x, int y, int w, int h, int l)
2858 {
2859 	unsigned char lbuf[(MAX_WIDTH * 2 + 7) / 8];
2860 	int i, j, k, j0, kk, dx, dy, wx, ww, yy, vf, tc, zoom = 1, scale = 1;
2861 
2862 
2863 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
2864 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
2865 	else scale = rint(can_zoom);
2866 
2867 	l *= 3;
2868 	dx = x % scale;
2869 	wx = x / scale;
2870 	ww = (w + dx + scale - 1) / scale;
2871 
2872 	dy = y % scale;
2873 	yy = y / scale;
2874 
2875 	/* Initial row fill */
2876 	mem_seg_scan(lbuf, yy, wx, ww, zoom, seg_preview);
2877 
2878 	tc = grid_rgb[GRID_SEGMENT];
2879 	j0 = !!dx;
2880 	vf = (scale == 1) | 2; // Draw both edges in one pixel if no zoom
2881 	for (k = dy , i = 0; i < h; i++ , k++)
2882 	{
2883 		unsigned char *tmp, *buf;
2884 		int nv;
2885 
2886 		if (k == scale)
2887 		{
2888 			mem_seg_scan(lbuf, ++yy, wx, ww, zoom, seg_preview);
2889 			k = 0;
2890 		}
2891 
2892 		/* Horizontal lines */
2893 		if (!k && (scale > 1))
2894 		{
2895 			tmp = rgb + i * l;
2896 			buf = lbuf;
2897 			nv = *buf++ + 0x100;
2898 			for (kk = dx, j = 0; j < w; j++ , tmp += 3)
2899 			{
2900 				if (nv & 1) // Draw grid line
2901 				{
2902 					tmp[0] = INT_2_R(tc);
2903 					tmp[1] = INT_2_G(tc);
2904 					tmp[2] = INT_2_B(tc);
2905 				}
2906 				if (++kk == scale)
2907 				{
2908 					if ((nv >>= 2) == 1) nv = *buf++ + 0x100;
2909 					kk = 0;
2910 				}
2911 			}
2912 		}
2913 
2914 		/* Vertical/mixed lines */
2915 		tmp = rgb + i * l + (j0 * scale - dx) * 3;
2916 		buf = lbuf;
2917 		nv = (*buf++ + 0x100) >> (j0 * 2);
2918 		for (j = j0; j < ww; j++ , tmp += scale * 3)
2919 		{
2920 			if (nv & vf)
2921 			{
2922 				tmp[0] = INT_2_R(tc);
2923 				tmp[1] = INT_2_G(tc);
2924 				tmp[2] = INT_2_B(tc);
2925 			}
2926 			if ((nv >>= 2) == 1) nv = *buf++ + 0x100;
2927 		}
2928 	}
2929 }
2930 
2931 /* Draw dashed line to RGB memory or straight to canvas */
draw_dash(int c0,int c1,int ofs,int x,int y,int w,int h,rgbcontext * ctx)2932 void draw_dash(int c0, int c1, int ofs, int x, int y, int w, int h, rgbcontext *ctx)
2933 {
2934 	rgbcontext cw;
2935 	unsigned char *dest;
2936 	int i, k, l, ww, step, cc[2] = { c0, c1 };
2937 
2938 	if (!ctx)
2939 	{
2940 		cw.xy[2] = (cw.xy[0] = x) + w;
2941 		cw.xy[3] = (cw.xy[1] = y) + h;
2942 		cw.rgb = NULL;
2943 		cmd_setv(drawing_canvas, ctx = &cw, CANVAS_PAINT);
2944 		if (!cw.rgb) return;
2945 	}
2946 	else if (!clip(cw.xy, x, y, x + w, y + h, ctx->xy)) return;
2947 
2948 	ofs += cw.xy[0] + cw.xy[1] - x - y; // offset in pattern
2949 	ww = (ctx->xy[2] - ctx->xy[0]) * 3;
2950 	if (w == 1) // Vertical
2951 	{
2952 		step = ww;
2953 		l = cw.xy[3] - cw.xy[1];
2954 	}
2955 	else // Horizontal
2956 	{
2957 		step = 3;
2958 		l = cw.xy[2] - cw.xy[0];
2959 	}
2960 	dest = ctx->rgb + (cw.xy[1] - ctx->xy[1]) * ww +
2961 		(cw.xy[0] - ctx->xy[0]) * 3;
2962 
2963 	for (i = 0; i < l; i++)
2964 	{
2965 		k = cc[((i + ofs) / 3) & 1];
2966 		dest[0] = INT_2_R(k);
2967 		dest[1] = INT_2_G(k);
2968 		dest[2] = INT_2_B(k);
2969 		dest += step;
2970 	}
2971 
2972 	if (ctx == &cw) cmd_setv(drawing_canvas, ctx, CANVAS_PAINT);
2973 }
2974 
2975 /* Polygon drawing to RGB memory */
draw_poly(int * xy,int cnt,int shift,int x00,int y00,rgbcontext * ctx)2976 void draw_poly(int *xy, int cnt, int shift, int x00, int y00, rgbcontext *ctx)
2977 {
2978 	linedata line;
2979 	unsigned char *rgb;
2980 	int i, x0, y0, x1, y1, dx, dy, a0, a, w, vxy[4];
2981 
2982 	copy4(vxy, ctx->xy);
2983 	w = vxy[2] - vxy[0];
2984 	--vxy[2]; --vxy[3];
2985 
2986 	x1 = x00 + *xy++; y1 = y00 + *xy++;
2987 	a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2:
2988 		y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 5;
2989 	for (i = 1; i < cnt; i++)
2990 	{
2991 		x0 = x1; y0 = y1; a0 = a;
2992 		x1 = x00 + *xy++; y1 = y00 + *xy++;
2993 		dx = abs(x1 - x0); dy = abs(y1 - y0);
2994 		if (dx < dy) dx = dy; shift += dx;
2995 		switch (a0) // Basic clipping
2996 		{
2997 		// Already visible - skip if same point
2998 		case 0: if (!dx) continue; break;
2999 		// Left of window - skip if still there
3000 		case 1: if (x1 < vxy[0]) continue; break;
3001 		// Right of window
3002 		case 2: if (x1 > vxy[2]) continue; break;
3003 		// Top of window
3004 		case 3: if (y1 < vxy[1]) continue; break;
3005 		// Bottom of window
3006 		case 4: if (y1 > vxy[3]) continue; break;
3007 		// First point - never skip
3008 		case 5: a0 = 0; break;
3009 		}
3010 		// May be visible - find where the other end goes
3011 		a = x1 < vxy[0] ? 1 : x1 > vxy[2] ? 2 :
3012 			y1 < vxy[1] ? 3 : y1 > vxy[3] ? 4 : 0;
3013 		line_init(line, x0, y0, x1, y1);
3014 		if (a0 + a) // If both ends inside area, no clipping needed
3015 		{
3016 			if (line_clip(line, vxy, &a0) < 0) continue;
3017 			dx -= a0;
3018 		}
3019 		// Draw to RGB
3020 		for (dx = shift - dx; line[2] >= 0; line_step(line) , dx++)
3021 		{
3022 			rgb = ctx->rgb + ((line[1] - ctx->xy[1]) * w +
3023 				(line[0] - ctx->xy[0])) * 3;
3024 			rgb[0] = rgb[1] = rgb[2] = ((~dx >> 2) & 1) * 255;
3025 		}
3026 	}
3027 }
3028 
3029 /* Clip area to image & align rgb pointer with it */
clip_to_image(int * rect,unsigned char * rgb,int * vxy)3030 static unsigned char *clip_to_image(int *rect, unsigned char *rgb, int *vxy)
3031 {
3032 	int rxy[4], mw, mh;
3033 
3034 	/* Clip update area to image bounds */
3035 	xy_origin(rxy, vxy, margin_main_x, margin_main_y);
3036 	canvas_size(&mw, &mh);
3037 	if (!clip(rect, 0, 0, mw, mh, rxy)) return (NULL);
3038 
3039 	/* Align buffer with image */
3040 	rgb += ((rxy[2] - rxy[0]) * (rect[1] - rxy[1]) + (rect[0] - rxy[0])) * 3;
3041 	return (rgb);
3042 }
3043 
3044 /* Map clipping rectangle to line-space, for use with line_clip() */
prepare_line_clip(int * lxy,int * vxy,int scale)3045 void prepare_line_clip(int *lxy, int *vxy, int scale)
3046 {
3047 	int i;
3048 
3049 	for (i = 0; i < 4; i++)
3050 		lxy[i] = floor_div(vxy[i] - margin_main_xy[i & 1] - (i >> 1), scale);
3051 }
3052 
canvas_render(u_render_state * u,int py,int ph)3053 static void canvas_render(u_render_state *u, int py, int ph)
3054 {
3055 	int cxy[4], rxy[4], pw = u->pw;
3056 	unsigned char *rgb = NULL;
3057 
3058 	/* Render underlying layers */
3059 	if (u->lr)
3060 	{
3061 		copy4(cxy, u->cxy);
3062 		rgb = u->rgb + (py - cxy[1]) * pw;
3063 		cxy[3] = (cxy[1] = py) + ph;
3064 		render_layers(rgb, cxy, pw, u->r.zoom, u->r.scale,
3065 			0, layer_selected - 1, FALSE);
3066 	}
3067 
3068 	/* Render canvas image */
3069 	if (u->irgb && clip(rxy, 0, py, INT_MAX, py + ph, u->r.rxy))
3070 		main_render(u, rxy[1], rxy[3] - rxy[1]);
3071 
3072 	/* Render overlying layers */
3073 	if (u->lr) render_layers(rgb, cxy, pw, u->r.zoom, u->r.scale,
3074 		layer_selected + 1, layers_total, FALSE);
3075 }
3076 
3077 #ifdef U_THREADS
3078 
do_canvas_render(tcb * thread)3079 static void do_canvas_render(tcb *thread)
3080 {
3081 	u_render_state *u = thread->data;
3082 	int *rr = u->lr ? u->cxy : u->r.rxy;
3083 	int y0 = rr[1], scale = u->r.scale, h = thread->nsteps * scale;
3084 	int d = floor_mod(y0, scale);
3085 
3086 	if (thread->step0) y0 += thread->step0 * scale - d;
3087 	else h -= d;
3088 	if (h > rr[3] - y0) h = rr[3] - y0;
3089 
3090 	canvas_render(u, y0, h);
3091 }
3092 
3093 #endif
3094 
3095 int kpix_threads;	// Min kpixels per render thread
3096 
paint_canvas(void * dt,void ** wdata,int what,void ** where,rgbcontext * ctx)3097 static int paint_canvas(void *dt, void **wdata, int what, void **where,
3098 	rgbcontext *ctx)
3099 {
3100 	u_render_state u;
3101 	unsigned char *irgb, *rgb = ctx->rgb;
3102 	int rect[4], vxy[4];
3103 	int i, px, py, pw, ph, zoom = 1, scale = 1, paste_f = FALSE;
3104 
3105 	pw = ctx->xy[2] - (px = ctx->xy[0]);
3106 	ph = ctx->xy[3] - (py = ctx->xy[1]);
3107 	memset(rgb, mem_background, pw * ph * 3);
3108 
3109 	/* Find out which part is image */
3110 	irgb = clip_to_image(rect, rgb, ctx->xy);
3111 
3112 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3113 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3114 	else scale = rint(can_zoom);
3115 
3116 	/* Prepare data for renderer */
3117 	memset(&u, 0, sizeof(u));
3118 	u.r.zoom = zoom;
3119 	u.r.scale = scale;
3120 	u.rgb = rgb;
3121 	u.irgb = irgb;
3122 	u.pw = pw * 3;
3123 	xy_origin(u.cxy, ctx->xy, margin_main_x, margin_main_y);
3124 
3125 	u.lr = layers_total && show_layers_main;
3126 
3127 	/* Set up image for renderer */
3128 	if (irgb)
3129 	{
3130 		copy4(u.r.rxy, rect);
3131 		main_render_req(&u);
3132 		paste_f = u.pflag;
3133 	}
3134 
3135 	if (bkg_flag && bkg_rgb) async_bk = render_bkg(ctx); /* Tracing image */
3136 	else if (!u.lr) /* Render default background if no layers shown */
3137 	{
3138 		if (irgb && ((mem_xpm_trans >= 0) ||
3139 			(!overlay_alpha && mem_img[CHN_ALPHA] && !channel_dis[CHN_ALPHA])))
3140 			async_bk = render_background(irgb, rect[0], rect[1],
3141 				rect[2] - rect[0], rect[3] - rect[1], pw * 3);
3142 	}
3143 
3144 	while (irgb || u.lr)
3145 	{
3146 #ifdef U_THREADS
3147 		int nt, nt2, pww = 0, wh = 0;
3148 		size_t vpix = 0;
3149 
3150 		/* Calculate amount of work for threads */
3151 		if (irgb)
3152 		{
3153 			wh = xy_span(rect, scale, 1);
3154 			pww = xy_span(rect, scale, 0);
3155 			vpix = pww * wh;
3156 		}
3157 		if (u.lr)
3158 		{
3159 			size_t vp2 = render_layers(NULL, u.cxy, 0, zoom, scale,
3160 				0, layers_total, FALSE);
3161 			if (!vp2) break; // Nothing here, move along
3162 			// !!! Heuristic weight; maybe 1/8 would be better?
3163 			vpix += vp2 / 4 - vpix / 4;
3164 
3165 			u.rgb += ((u.cxy[1] - py + margin_main_y) * pw +
3166 				u.cxy[0] - px + margin_main_x) * 3;
3167 
3168 			wh = xy_span(u.cxy, scale, 1);
3169 			pww = xy_span(u.cxy, async_bk ? 1 : scale, 0);
3170 		}
3171 
3172 		nt = image_threads(pww, wh);
3173 		nt2 = ceil_div(vpix, kpix_threads * 1024);
3174 		if (nt2 > nt) nt2 = nt;
3175 
3176 #ifndef HAVE__SFA
3177 		/* csel_scan() takes 50%+ of per-row time, so when it cannot share
3178 		 * its color cache, 2 threads can interleave calls to it but more
3179 		 * than 2 only waste even more time on lock contention - WJ */
3180 		if ((nt2 > 2) && (u.m.overlay_s || (mem_cselect &&
3181 			(u.tflag | u.xflag | u.gflag | u.pflag)))) nt2 = 2;
3182 #endif
3183 
3184 		u.tdata = talloc(MA_SKIP_ZEROSIZE | MA_FLAG_NONE, nt2,
3185 			&u, sizeof(u), NULL,
3186 #else /* ifndef U_THREADS */
3187 		u.tdata = multialloc(MA_SKIP_ZEROSIZE | MA_FLAG_NONE,
3188 #endif
3189 			&u.m.rgb, u.m.rgb_s,
3190 			&u.m.mask, u.m.mask_s,
3191 			&u.m.overlay, u.m.overlay_s,
3192 			&u.m.channel, u.m.channel_s,
3193 			&u.m.alpha, u.m.alpha_s,
3194 			&u.m.n_channel, u.m.n_channel_s,
3195 			&u.m.n_alpha, u.m.n_alpha_s,
3196 			&u.m.n_opacity, u.m.n_opacity_s,
3197 			NULL);
3198 
3199 #ifdef U_THREADS
3200 		if (u.tdata && (u.tdata != MEM_NONE)) // Threads w/allocation
3201 		{
3202 			/* !!! do_transform(), and any other preview code that
3203 			 * self-initializes global tables, should get an init
3204 			 * call here to avoid a thread clash - WJ */
3205 			if (u.tflag) do_transform(0, 0, 0, NULL, NULL, NULL, 255);
3206 
3207 			nt /= u.tdata->count;
3208 			if (nt > MAX_TH_STRIPS) nt = MAX_TH_STRIPS;
3209 			u.tdata->chunks = nt;
3210 			u.tdata->silent = TRUE;
3211 			launch_threads(do_canvas_render, u.tdata, NULL, wh);
3212 		}
3213 		else
3214 #endif
3215 		if (u.tdata) // Single thread
3216 		{
3217 			int *rr = u.lr ? u.cxy : rect;
3218 			canvas_render(&u, rr[1], rr[3] - rr[1]);
3219 		}
3220 		if (u.tdata != MEM_NONE) free(u.tdata);
3221 		break;
3222 	}
3223 
3224 	/* No grid at all */
3225 	if (!mem_show_grid || (scale < mem_grid_min));
3226 	/* No paste - single area */
3227 	else if (!paste_f) draw_grid(rgb, px, py, pw, ph, pw);
3228 	/* With paste - zero to four areas */
3229 	else
3230 	{
3231 		int n, x0, y0, w0, h0, rect04[5 * 4], *p = rect04;
3232 		unsigned char *r;
3233 
3234 		w0 = (marq_x2 < mem_width ? marq_x2 + 1 : mem_width) * scale;
3235 		x0 = marq_x1 > 0 ? marq_x1 * scale : 0;
3236 		w0 -= x0; x0 += margin_main_x;
3237 		h0 = (marq_y2 < mem_height ? marq_y2 + 1 : mem_height) * scale;
3238 		y0 = marq_y1 > 0 ? marq_y1 * scale : 0;
3239 		h0 -= y0; y0 += margin_main_y;
3240 
3241 		n = clip4(rect04, px, py, pw, ph, x0, y0, w0, h0);
3242 		while (n--)
3243 		{
3244 			p += 4;
3245 			r = rgb + ((p[1] - py) * pw + (p[0] - px)) * 3;
3246 			draw_grid(r, p[0], p[1], p[2], p[3], pw);
3247 		}
3248 	}
3249 
3250 	/* Tile grid */
3251 	if (show_tile_grid && irgb) draw_tgrid(irgb, rect[0], rect[1],
3252 		rect[2] - rect[0], rect[3] - rect[1], pw);
3253 
3254 	/* Segmentation preview */
3255 	if (seg_preview && irgb) draw_segments(irgb, rect[0], rect[1],
3256 		rect[2] - rect[0], rect[3] - rect[1], pw);
3257 
3258 	async_bk = FALSE;
3259 
3260 /* !!! All other over-the-image things have to be redrawn here as well !!! */
3261 	prepare_line_clip(vxy, ctx->xy, scale);
3262 	/* Redraw gradient line if needed */
3263 	i = gradient[mem_channel].status;
3264 	if ((mem_gradient || (tool_type == TOOL_GRADIENT)) &&
3265 		(mouse_left_canvas ? (i == GRAD_DONE) : (i != GRAD_NONE)))
3266 		refresh_line(3, vxy, ctx);
3267 
3268 	/* Draw marquee as we may have drawn over it */
3269 	if ((marq_status != MARQUEE_NONE) && irgb)
3270 		paint_marquee(MARQ_SHOW, 0, 0, ctx);
3271 	if ((tool_type == TOOL_POLYGON) && poly_points)
3272 		paint_poly_marquee(ctx);
3273 
3274 	/* Redraw line if needed */
3275 	if ((((tool_type == TOOL_POLYGON) && (poly_status == POLY_SELECTING)) ||
3276 		((tool_type == TOOL_LINE) && (line_status == LINE_LINE))) &&
3277 		!mouse_left_canvas)
3278 		refresh_line(tool_type == TOOL_LINE ? 1 : 2, vxy, ctx);
3279 
3280 	/* Redraw perimeter if needed */
3281 	if (perim_status) repaint_perim(ctx);
3282 
3283 	return (TRUE); // now draw this
3284 }
3285 
repaint_canvas(int px,int py,int pw,int ph)3286 void repaint_canvas(int px, int py, int pw, int ph)
3287 {
3288 	rgbcontext ctx = { { px, py, px + pw, py + ph }, NULL };
3289 
3290 	cmd_setv(drawing_canvas, &ctx, CANVAS_PAINT);
3291 	if (!ctx.rgb) return;
3292 	paint_canvas(NULL, NULL, 0, NULL, &ctx);
3293 	cmd_setv(drawing_canvas, &ctx, CANVAS_PAINT);
3294 }
3295 
3296 /* Update x,y,w,h area of current image */
main_update_area(int x,int y,int w,int h)3297 void main_update_area(int x, int y, int w, int h)
3298 {
3299 	int zoom, scale, rxy[4];
3300 
3301 	if (can_zoom < 1.0)
3302 	{
3303 		zoom = rint(1.0 / can_zoom);
3304 		w += x;
3305 		h += y;
3306 		x = floor_div(x + zoom - 1, zoom);
3307 		y = floor_div(y + zoom - 1, zoom);
3308 		w = (w - x * zoom + zoom - 1) / zoom;
3309 		h = (h - y * zoom + zoom - 1) / zoom;
3310 		if ((w <= 0) || (h <= 0)) return;
3311 	}
3312 	else
3313 	{
3314 		scale = rint(can_zoom);
3315 		x *= scale;
3316 		y *= scale;
3317 		w *= scale;
3318 		h *= scale;
3319 		if (color_grid && mem_show_grid && (scale >= mem_grid_min))
3320 			w++ , h++; // Redraw grid lines bordering the area
3321 	}
3322 
3323 	rxy[2] = (rxy[0] = x + margin_main_x) + w;
3324 	rxy[3] = (rxy[1] = y + margin_main_y) + h;
3325 	cmd_setv(drawing_canvas, rxy, CANVAS_REPAINT);
3326 }
3327 
3328 /* Get zoomed canvas size */
canvas_size(int * w,int * h)3329 void canvas_size(int *w, int *h)
3330 {
3331 	int zoom = 1, scale = 1;
3332 
3333 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3334 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3335 	else scale = rint(can_zoom);
3336 
3337 	*w = (mem_width * scale + zoom - 1) / zoom;
3338 	*h = (mem_height * scale + zoom - 1) / zoom;
3339 }
3340 
clear_perim(perim_info * p)3341 static void clear_perim(perim_info *p)
3342 {
3343 	int ps = p->mode;
3344 	p->mode = 0; /* Cleared */
3345 	clear_perim_real(p->x, p->y, p->s);
3346 	if (ps == TOOL_CLONE + 1)
3347 		clear_perim_real(p->x + p->cx, p->y + p->cy, p->s);
3348 }
3349 
repaint_perim_real(int c,int ox,int oy,rgbcontext * ctx)3350 static void repaint_perim_real(int c, int ox, int oy, rgbcontext *ctx)
3351 {
3352 	int w, h, x0, y0, x1, y1, zoom = 1, scale = 1;
3353 
3354 
3355 	/* !!! This uses the fact that zoom factor is either N or 1/N !!! */
3356 	if (can_zoom < 1.0) zoom = rint(1.0 / can_zoom);
3357 	else scale = rint(can_zoom);
3358 
3359 	ox += perim_x; oy += perim_y;
3360 	x0 = margin_main_x + (ox * scale) / zoom;
3361 	y0 = margin_main_y + (oy * scale) / zoom;
3362 	x1 = margin_main_x + ((ox + perim_s - 1) * scale) / zoom + scale - 1;
3363 	y1 = margin_main_y + ((oy + perim_s - 1) * scale) / zoom + scale - 1;
3364 
3365 	w = x1 - x0 + 1;
3366 	h = y1 - y0 + 1;
3367 
3368 	draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0, y0, 1, h, ctx);
3369 	draw_dash(c, RGB_2_INT(0, 0, 0), 0, x1, y0, 1, h, ctx);
3370 
3371 	draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0 + 1, y0, w - 2, 1, ctx);
3372 	draw_dash(c, RGB_2_INT(0, 0, 0), 0, x0 + 1, y1, w - 2, 1, ctx);
3373 }
3374 
repaint_perim(rgbcontext * ctx)3375 static void repaint_perim(rgbcontext *ctx)
3376 {
3377 	repaint_perim_real(RGB_2_INT(255, 255, 255), 0, 0, ctx);
3378 	if (perim_status == TOOL_CLONE + 1)
3379 		repaint_perim_real(RGB_2_INT(255, 0, 0), perim_cx, perim_cy, ctx);
3380 }
3381 
move_perim(int x,int y)3382 static void move_perim(int x, int y)
3383 {
3384 	perim_info p = perim_state;
3385 
3386 	perim_status = 0; /* Clear */
3387 	perim_wx = x; /* Remember */
3388 	perim_wy = y;
3389 	if ((tool_size * can_zoom > 4) && !NO_PERIM(tool_type) && !mouse_left_canvas)
3390 	{
3391 		perim_x = x - (tool_size >> 1);
3392 		perim_y = y - (tool_size >> 1);
3393 		perim_s = tool_size;
3394 		perim_cx = clone_dx;
3395 		perim_cy = clone_dy;
3396 		perim_status = tool_type + 1; /* Draw */
3397 	}
3398 	if (p.mode) clear_perim(&p);
3399 	if (perim_status) repaint_perim(NULL);
3400 }
3401 
configure_canvas()3402 static void configure_canvas()
3403 {
3404 	int new_margin_x = 0, new_margin_y = 0;
3405 
3406 	if (canvas_image_centre)
3407 	{
3408 		int w, h, wh[2];
3409 		cmd_peekv(drawing_canvas, wh, sizeof(wh), CANVAS_SIZE);
3410 		canvas_size(&w, &h);
3411 		if ((wh[0] -= w) > 0) new_margin_x = wh[0] >> 1;
3412 		if ((wh[1] -= h) > 0) new_margin_y = wh[1] >> 1;
3413 	}
3414 
3415 	if ((new_margin_x != margin_main_x) || (new_margin_y != margin_main_y))
3416 	{
3417 		margin_main_x = new_margin_x;
3418 		margin_main_y = new_margin_y;
3419 		cmd_repaint(drawing_canvas);
3420 			// Force redraw of whole canvas as the margin has shifted
3421 	}
3422 	vw_realign();	// Update the view window as needed
3423 }
3424 
force_main_configure()3425 void force_main_configure()
3426 {
3427 	if (cmd_mode) return;
3428 	if (drawing_canvas) configure_canvas();
3429 	if (view_showing && vw_drawing) vw_configure();
3430 }
3431 
set_cursor(void ** what)3432 void set_cursor(void **what)	// Set mouse cursor
3433 {
3434 	cmd_cursor(drawing_canvas, !cursor_tool ? NULL : what ? what :
3435 		m_cursor[tool_type]);
3436 }
3437 
change_to_tool(int icon)3438 void change_to_tool(int icon)
3439 {
3440 	grad_info *grad;
3441 	void *var, **slot = icon_buttons[icon];
3442 	int i, t, update = UPD_SEL;
3443 
3444 	if (!cmd_checkv(slot, SLOT_SENSITIVE)) return; // Blocked
3445 // !!! Unnecessarily complicated approach - better add a peek operation
3446 	var = cmd_read(slot, NULL);
3447 	if (*(int *)var != TOOL_ID(slot)) cmd_set(slot, TRUE);
3448 
3449 	switch (icon)
3450 	{
3451 	case TTB_PAINT:
3452 		t = brush_type; break;
3453 	case TTB_SHUFFLE:
3454 		t = TOOL_SHUFFLE; break;
3455 	case TTB_FLOOD:
3456 		t = TOOL_FLOOD; break;
3457 	case TTB_LINE:
3458 		t = TOOL_LINE; break;
3459 	case TTB_SMUDGE:
3460 		t = TOOL_SMUDGE; break;
3461 	case TTB_CLONE:
3462 		t = TOOL_CLONE; break;
3463 	case TTB_SELECT:
3464 		t = TOOL_SELECT; break;
3465 	case TTB_POLY:
3466 		t = TOOL_POLYGON; break;
3467 	case TTB_GRAD:
3468 		t = TOOL_GRADIENT; break;
3469 	default: return;
3470 	}
3471 
3472 	/* Tool hasn't changed (likely, recursion changed it from under us) */
3473 	if (t == tool_type) return;
3474 
3475 	/* Make sure tool release actions are done */
3476 	tool_done();
3477 
3478 	if (perim_status) clear_perim(&perim_state);
3479 	i = tool_type;
3480 	tool_type = t;
3481 
3482 	grad = gradient + mem_channel;
3483 	if (i == TOOL_LINE) stop_line();
3484 	if ((i == TOOL_GRADIENT) && (grad->status != GRAD_NONE))
3485 	{
3486 		if (grad->status != GRAD_DONE) grad->status = GRAD_NONE;
3487 		else if (grad_opacity) update |= CF_DRAW;
3488 		else if (!mem_gradient) repaint_grad(NULL);
3489 	}
3490 	if ( marq_status != MARQUEE_NONE)
3491 	{
3492 		if (!script_cmds && paste_commit && (marq_status >= MARQUEE_PASTE))
3493 		{
3494 			commit_paste(FALSE, NULL);
3495 			pen_down = 0;
3496 			mem_undo_prepare();
3497 		}
3498 
3499 		marq_status = MARQUEE_NONE;	// Marquee is on so lose it!
3500 		update |= CF_DRAW;			// Needed to clear selection
3501 	}
3502 	if ( poly_status != POLY_NONE)
3503 	{
3504 		poly_status = POLY_NONE;	// Marquee is on so lose it!
3505 		poly_points = 0;
3506 		update |= CF_DRAW;			// Needed to clear selection
3507 	}
3508 	if (tool_type == TOOL_CLONE) init_clone();
3509 	/* Persistent selection frame */
3510 // !!! To NOT show selection frame while placing gradient
3511 //	if ((tool_type == TOOL_SELECT)
3512 	if (((tool_type == TOOL_SELECT) || (tool_type == TOOL_GRADIENT))
3513 		&& (marq_x1 >= 0) && (marq_y1 >= 0)
3514 		&& (marq_x2 >= 0) && (marq_y2 >= 0))
3515 	{
3516 		marq_status = MARQUEE_DONE;
3517 		paint_marquee(MARQ_SHOW, 0, 0, NULL);
3518 	}
3519 	if ((tool_type == TOOL_GRADIENT) && (grad->status != GRAD_NONE))
3520 	{
3521 		if (grad_opacity) update |= CF_DRAW;
3522 		else repaint_grad(NULL);
3523 	}
3524 	update_stuff(update);
3525 	move_perim(perim_wx, perim_wy); // New perimeter in old location
3526 }
3527 
pressed_view_hori(int state)3528 static void pressed_view_hori(int state)
3529 {
3530 	view_vsplit = !!state;
3531 	if (view_showing) view_show(); // rearrange
3532 }
3533 
set_image(int state)3534 void set_image(int state)
3535 {
3536 	static int depth = 0;
3537 
3538 	if (state ? --depth : depth++) return;
3539 
3540 	cmd_showhide(main_split, state);
3541 }
3542 
read_hex_dub(char * in)3543 static char read_hex_dub(char *in)	// Read hex double
3544 {
3545 	static const char chars[] = "0123456789abcdef0123456789ABCDEF";
3546 	const char *p1, *p2;
3547 
3548 	p1 = strchr(chars, in[0]);
3549 	p2 = strchr(chars, in[1]);
3550 	return (p1 && p2 ? (((p1 - chars) & 15) << 4) + ((p2 - chars) & 15) : '?');
3551 }
3552 
parse_drag(main_dd * dt,void ** wdata,int what,void ** where,drag_ext * drag)3553 static void parse_drag(main_dd *dt, void **wdata, int what, void **where,
3554 	drag_ext *drag)
3555 {
3556 #ifdef WIN32
3557 	char fname[PATHTXT];
3558 #else
3559 	char fname[PATHBUF];
3560 #endif
3561 	char ch, *tp, *tp2, *txt = drag->data;
3562 	int i, j, nlayer = TRUE;
3563 
3564 
3565 	if (drag->len <= 0) return;
3566 
3567 	set_image(FALSE);
3568 
3569 	tp = txt;
3570 	while ((layers_total < MAX_LAYERS) && (tp2 = strstr(tp, "file:")))
3571 	{
3572 		tp = tp2 + 5;
3573 		while (*tp == '/') tp++;
3574 #ifndef WIN32
3575 		// If not windows keep a leading slash
3576 		tp--;
3577 		// If windows strip away all leading slashes
3578 #endif
3579 		i = 0;
3580 		while ((ch = *tp++) > 31) // Copy filename
3581 		{
3582 			if (ch == '%')	// Weed out those ghastly % substitutions
3583 			{
3584 				ch = read_hex_dub(tp);
3585 				tp += 2;
3586 			}
3587 			fname[i++] = ch;
3588 			if (i >= sizeof(fname) - 1) break;
3589 		}
3590 		fname[i] = 0;
3591 		tp--;
3592 
3593 #ifdef WIN32
3594 		/* !!! GTK+ uses UTF-8 encoding for URIs on Windows */
3595 		gtkncpy(fname, fname, PATHBUF);
3596 		reseparate(fname);
3597 #endif
3598 		j = detect_image_format(fname);
3599 		if ((j > 0) && (j != FT_NONE) && (j != FT_LAYERS1))
3600 		{
3601 			if (!nlayer || layer_add(0, 0, 1, 0, mem_pal_def, 0))
3602 				nlayer = load_image(fname, FS_LAYER_LOAD, j) == 1;
3603 			if (nlayer) set_new_filename(layers_total, fname);
3604 		}
3605 	}
3606 	if (!nlayer) layer_delete(layers_total);
3607 
3608 	layer_refresh_list();
3609 	layer_choose(layers_total);
3610 	layers_notify_changed();
3611 	if (layers_total) view_show();
3612 	set_image(TRUE);
3613 }
3614 
3615 
3616 static clipform_dd uri_list = { "text/uri-list" };
3617 
3618 
3619 static void pressed_pal_copy();
3620 static void pressed_pal_paste();
3621 static void pressed_sel_ramp(int vert);
3622 
3623 static const signed char arrow_dx[4] = { 0, -1, 1, 0 },
3624 	arrow_dy[4] = { 1, 0, 0, -1 };
3625 
move_marquee(int action,int * xy,int change,int dir)3626 static void move_marquee(int action, int *xy, int change, int dir)
3627 {
3628 	int dx = tgrid_dx, dy = tgrid_dy;
3629 	if (!tgrid_snap) dx = dy = change;
3630 	paint_marquee(action,
3631 		xy[0] + dx * arrow_dx[dir], xy[1] + dy * arrow_dy[dir], NULL);
3632 	update_stuff(UPD_SGEOM);
3633 }
3634 
3635 ///	SCRIPTING
3636 
3637 /* Number of args is ((char **)res[0] - res - 1); res[] is NULL-terminated */
wj_parse_argv(char * src)3638 char **wj_parse_argv(char *src)
3639 {
3640 	char c, q, q0, *dest, *tmp, **v;
3641 	int n = 0, l = strlen(src);
3642 
3643 	dest = tmp = malloc(l + 1);
3644 	q0 = q = ' '; // Start outside word
3645 	while ((c = *src++))
3646 	{
3647 		/* Various quoted states */
3648 		if (q == '#')
3649 		{
3650 			if (c == '\n') q = ' ';
3651 		}
3652 		else if (q == '\\')
3653 		{
3654 			q = q0;
3655 			if (q == '"')
3656 			{
3657 				if ((c != '"') && (c != '\\') && (c != '`') &&
3658 					(c != '$') && (c != '\n')) *dest++ = '\\';
3659 				*dest++ = c;
3660 			}
3661 			else if (c != '\n')
3662 			{
3663 				*dest++ = c;
3664 				q = 0;
3665 			}
3666 		}
3667 		else if (q == '"')
3668 		{
3669 			if (c == '\\') q0 = q , q = c;
3670 			else if (c == '"') q = 0;
3671 			else *dest++ = c;
3672 		}
3673 		else if (q == '\'')
3674 		{
3675 			if (c == '\'') q = 0;
3676 			else *dest++ = c;
3677 		}
3678 		/* Unquoted state - in a word or in between */
3679 		else if ((c == '\n') || (c == ' ') || (c == '\t'))
3680 		{
3681 			if (!q) *dest++ = 0 , n++;
3682 			q = ' ';
3683 		}
3684 		else if (c == '\\') q0 = q , q = c;
3685 		else if ((c == '#') && q) q = c;
3686 		else if ((c == '"') || (c == '\'')) q = c;
3687 		else
3688 		{
3689 			*dest++ = c;
3690 			q = 0;
3691 		}
3692 	}
3693 	/* Final state */
3694 	if (!q) *dest++ = 0 , n++;
3695 	else if ((q == '\\') || (q == '"') || (q == '\'')) n = -1; // Error
3696 	l = dest - tmp;
3697 	if ((n > 0) && (v = realloc(tmp, sizeof(*v) * (n + 1) + l)))
3698 	{
3699 		tmp = (void *)(v + n + 1);
3700 		memmove(tmp, v, l);
3701 		for (l = 0; l < n; l++)
3702 		{
3703 			v[l] = tmp;
3704 			tmp += strlen(tmp) + 1;
3705 		}
3706 		v[n] = NULL;
3707 		return (v);
3708 	}
3709 	/* Syntax error, or empty string */
3710 	free(tmp);
3711 	return (NULL);
3712 }
3713 
command_slot(char * cmd)3714 static void **command_slot(char *cmd)
3715 {
3716 	void **slot;
3717 	int l, m;
3718 
3719 	l = strspn(++cmd, "/");
3720 	l += strcspn(cmd + l, "=/");
3721 	slot = find_slot(NEXT_SLOT(main_menubar), cmd, l,
3722 		(cmd[0] != '/') && (cmd[l] == '/'));
3723 	m = 2;
3724 	while (slot && (cmd[l] == '/'))
3725 	{
3726 		cmd += l + 1;
3727 		l = strcspn(cmd, "=/");
3728 		slot = find_slot(NEXT_SLOT(slot), cmd, l, m++);
3729 	}
3730 	return (slot);
3731 }
3732 
3733 #define MAX_NESTING 16 /* Defuse recursion bombs */
3734 
run_script(char ** res)3735 int run_script(char **res)
3736 {
3737 	static int level;
3738 	void **slot;
3739 	char **cur, *str = NULL, *err = NULL;
3740 	int n;
3741 
3742 	level++;
3743 	if (!res || !res[0]) err = _("Empty string or broken quoting");
3744 	else if (res[0][0] != '-') err = _("Script must begin with a command");
3745 	else if (level > MAX_NESTING) err = _("Script nesting limit exceeded");
3746 	else
3747 	{
3748 		user_break = 0;
3749 		for (cur = res; cur[0]; cur++)
3750 		{
3751 			if (cur[0][0] != '-') continue; // Skip to next command
3752 			if (!strcmp(cur[0], "--")) break; // End marker
3753 			slot = command_slot(cur[0]);
3754 			if (!slot) str = _("'%s' does not match any item");
3755 			else if (!cmd_checkv(slot, SLOT_SCRIPTABLE))
3756 				str = _("'%s' matches a non-scriptable item");
3757 			else if (!cmd_checkv(slot, SLOT_SENSITIVE))
3758 				str = _("'%s' matches a disabled item");
3759 			else
3760 			{
3761 				char *chain[3], *tmp = strchr(cur[0], '=');
3762 
3763 				script_cmds = cur + 1;
3764 				/* Send default value on, if no toggle-item */
3765 				if (tmp && !slot_data(slot, NULL))
3766 				{
3767 					chain[0] = tmp;
3768 					chain[1] = (void *)&chain[1];
3769 					chain[2] = (void *)script_cmds;
3770 					script_cmds = chain;
3771 				}
3772 
3773 				/* Activate the item */
3774 				n = cmd_setstr(slot, tmp + !!tmp); // skip "="
3775 				script_cmds = NULL;
3776 
3777 				if (n < 0) str = _("'%s' value does not fit item");
3778 				else if (!user_break) continue;
3779 			}
3780 			break; // Terminate on error
3781 		}
3782 		if (str) err = str = g_strdup_printf(__(str), cur[0]);
3783 		update_stuff(CF_NOW); // Do cumulative update
3784 	}
3785 	level--;
3786 	if (err) alert_box(_("Error"), err, NULL);
3787 	if (str) g_free(str);
3788 	return (!err ? 1 : str ? -1 : 0); // 1 = clean, -1 = buggy, 0 = wrong
3789 }
3790 
3791 #define SCRIPT_ITEMS 10
3792 #define SCRIPTS_MAX FACTION_ROWS_TOTAL
3793 #define MAXNAMELEN 2048
3794 
3795 #define SCRIPT1_NAME "# Resharpen image"
3796 #define SCRIPT1_CODE "# Resharpen image after rescaling\n" \
3797 	"-effect/unsharp r=1 am=0.4 -effect/unsharp r=30 am=0.1"
3798 
3799 static char *script_ini[2] = { "scriptName%d", "script%d" };
3800 
launch_script(int n)3801 static void launch_script(int n)
3802 {
3803 	char txt[64], **res;
3804 
3805 	sprintf(txt, script_ini[1], n);
3806 	res = wj_parse_argv(inifile_get(txt, ""));
3807 	run_script(res);
3808 	free(res);
3809 }
3810 
update_script_menu()3811 static void update_script_menu()	// Populate menu
3812 {
3813 	int i, v, items = 0;
3814 	char txt[64], *nm, *sm, *ns = SCRIPT1_NAME, *ss = SCRIPT1_CODE;
3815 	void **slot;
3816 
3817 	/* Show valid slots in menu */
3818 	for (i = 1; i <= SCRIPT_ITEMS; i++)
3819 	{
3820 		sprintf(txt, script_ini[0], i);
3821 		nm = inifile_get(txt, ns);
3822 
3823 		sprintf(txt, script_ini[1], i);
3824 		sm = inifile_get(txt, ss);
3825 
3826 		slot = menu_slots[MENU_SCRIPT1 - 1 + i];
3827 
3828 		if ((v = nm && nm[0] && (nm[0] != '#') && sm && sm[0]))
3829 			cmd_setv(slot, nm, LABEL_VALUE);
3830 
3831 		cmd_showhide(slot, v); // Hide by default
3832 		cmd_sensitive(slot, v); // Make insensitive for shortcuts
3833 		items += v;
3834 		ns = ss = "";
3835 	}
3836 
3837 	/* Hide submenu if no valid slots */
3838 	cmd_showhide(menu_slots[MENU_SCRIPT_M], items);
3839 	cmd_showhide(menu_slots[MENU_SCRIPT], !items);
3840 }
3841 
3842 typedef struct {
3843 	char *rows[SCRIPTS_MAX][4];
3844 	char *script, *name;
3845 	void **list, **nm, **tx, **exec;
3846 	int nidx, idx, cnt, lock, changed;
3847 } script_dd;
3848 
update_text(script_dd * dt)3849 static void update_text(script_dd *dt)
3850 {
3851 	if (dt->changed)
3852 	{
3853 		char **rp = dt->rows[dt->idx];
3854 		cmd_read(dt->tx, dt);
3855 		if (rp[1] != rp[3]) free(rp[1]);
3856 		rp[1] = strdup(dt->script);
3857 		dt->changed = FALSE;
3858 	}
3859 }
3860 
script_click(script_dd * dt,void ** wdata,int what,void ** where)3861 static void script_click(script_dd *dt, void **wdata, int what, void **where)
3862 {
3863 	char txt[64], **rp;
3864 	int i, idx[SCRIPTS_MAX];
3865 
3866 	if (what == op_EVT_DESTROY) // Finalize
3867 	{
3868 		cmd_sensitive(menu_slots[MENU_SCRIPT], TRUE);
3869 		cmd_sensitive(menu_slots[MENU_SCRIPTC], TRUE);
3870 		return;
3871 	}
3872 
3873 	if (what != op_EVT_CANCEL) update_text(dt);
3874 
3875 	if (origin_slot(where) == dt->exec) // Run script
3876 	{
3877 		char **res = wj_parse_argv(dt->rows[dt->idx][1]);
3878 		run_script(res);
3879 		free(res);
3880 		return;
3881 	}
3882 
3883 	cmd_showhide(wdata, FALSE);
3884 	if (what != op_EVT_CANCEL) // Store scripts
3885 	{
3886 		cmd_peekv(dt->list, idx, sizeof(idx), LISTC_ORDER);
3887 		/* Copy out reordered strings */
3888 		for (i = 0; i < SCRIPTS_MAX; i++)
3889 		{
3890 			if (idx[i] == i) continue;
3891 			rp = dt->rows[i];
3892 			if (rp[0] == rp[2]) rp[0] = strdup(rp[0]);
3893 			if (rp[1] == rp[3]) rp[1] = strdup(rp[1]);
3894 		}
3895 		/* Store into inifile */
3896 		for (i = 0; i < SCRIPTS_MAX; i++)
3897 		{
3898 			rp = dt->rows[i];
3899 			sprintf(txt, script_ini[0], idx[i] + 1);
3900 			if (rp[0] != rp[2]) inifile_set(txt, rp[0]);
3901 			sprintf(txt, script_ini[1], idx[i] + 1);
3902 			if (rp[1] != rp[3]) inifile_set(txt, rp[1]);
3903 		}
3904 		/* Display in menu */
3905 		update_script_menu();
3906 	}
3907 
3908 	for (i = 0; i < SCRIPTS_MAX; i++) // Release string memory
3909 	{
3910 		rp = dt->rows[i];
3911 		if (rp[0] != rp[2]) free(rp[0]);
3912 		if (rp[1] != rp[3]) free(rp[1]);
3913 	}
3914 
3915 	run_destroy(wdata);
3916 }
3917 
script_changed(script_dd * dt,void ** wdata,int what,void ** where)3918 static void script_changed(script_dd *dt, void **wdata, int what, void **where)
3919 {
3920 	char **rp;
3921 
3922 	if (dt->lock) return;
3923 	if (origin_slot(where) == dt->tx) // Text entry
3924 	{
3925 		dt->changed = TRUE; // Read it later
3926 		return;
3927 	}
3928 
3929 	cmd_read(where, dt); // Name entry
3930 	rp = dt->rows[dt->idx];
3931 	if (rp[0] == rp[2]) rp[0] = malloc(MAXNAMELEN);
3932 	strncpy0(rp[0], dt->name, MAXNAMELEN);
3933 	cmd_setv(dt->list, (void *)dt->idx, LISTC_RESET_ROW);
3934 }
3935 
script_select_row(script_dd * dt,void ** wdata,int what,void ** where)3936 static void script_select_row(script_dd *dt, void **wdata, int what, void **where)
3937 {
3938 	char **rp;
3939 
3940 	cmd_read(where, dt);
3941 
3942 	if (dt->nidx == dt->idx) return; // no change
3943 	dt->lock = TRUE;
3944 
3945 	/* Update outgoing row */
3946 	update_text(dt);
3947 
3948 	/* Get fields from array for incoming row */
3949 	rp = dt->rows[dt->idx = dt->nidx];
3950 	cmd_setv(dt->nm, rp[0], ENTRY_VALUE);
3951 	cmd_setv(dt->tx, rp[1], TEXT_VALUE);
3952 
3953 	dt->lock = FALSE;
3954 }
3955 
3956 #define WBbase script_dd
3957 static void *script_code[] = {
3958 	WINDOW(_("Script")), // nonmodal
3959 //	WXYWH("script", 400, 400),
3960 	DEFSIZE(400, 400),
3961 	EVENT(DESTROY, script_click),
3962 	HSEP,
3963 	XVBOXB,
3964 	BORDER(SCROLL, 0), BORDER(FRAME, 0), BORDER(ENTRY, 0),
3965 	VSPLIT,
3966 	XSCROLL(1, 1), // auto/auto; 1st page
3967 		WLIST,
3968 		PTXTCOLUMN(rows[0][0], WBsizeof(rows[0]), 200, 0),
3969 		REF(list), LISTCd(nidx, cnt, script_select_row), TRIGGER,
3970 	VBOXS, // 2nd page
3971 		FHBOXB(_("Action")),
3972 		REF(nm), XENTRY(name), EVENT(CHANGE, script_changed),
3973 		WDONE, // FHBOX
3974 		REF(tx), TEXT(script), EVENT(CHANGE, script_changed),
3975 	WDONE, // VBOX
3976 	WDONE, // VSPLIT
3977 	WDONE, // XVBOX
3978 	HSEP,
3979 	EQBOX, CANCELBTN(_("Cancel"), script_click),
3980 		REF(exec), BUTTON(_("Execute"), script_click),
3981 		BUTTON(_("OK"), script_click), FOCUS,
3982 	WSHOW
3983 };
3984 #undef WBbase
3985 
pressed_script()3986 static void pressed_script()
3987 {
3988 	script_dd tdata;
3989 	char txt[64], *ns = SCRIPT1_NAME, *ss = SCRIPT1_CODE;
3990 	int i;
3991 
3992 	// Make sure the user can only open 1 script window
3993 	cmd_sensitive(menu_slots[MENU_SCRIPT], FALSE);
3994 	cmd_sensitive(menu_slots[MENU_SCRIPTC], FALSE);
3995 
3996 	memset(&tdata, 0, sizeof(tdata));
3997 	for (i = 0; i < SCRIPTS_MAX; i++)
3998 	{
3999 		sprintf(txt, script_ini[0], i + 1);
4000 		tdata.rows[i][0] = tdata.rows[i][2] = inifile_get(txt, ns);
4001 		sprintf(txt, script_ini[1], i + 1);
4002 		tdata.rows[i][1] = tdata.rows[i][3] = inifile_get(txt, ss);
4003 		ns = ss = "";
4004 	}
4005 
4006 	tdata.idx = 1;
4007 	tdata.cnt = SCRIPTS_MAX;
4008 	run_create(script_code, &tdata, sizeof(tdata));
4009 }
4010 
4011 typedef struct {
4012 	char *name;
4013 } name_dd;
4014 
4015 #define WBbase name_dd
4016 static void *name_code[] = {
4017 	TOPVBOX, uPATHSTR(name), WSHOW
4018 };
4019 #undef WBbase
4020 
load_script()4021 static void load_script()
4022 {
4023 	name_dd *dt, tdata = { NULL };
4024 	char *txt, *tx2 = NULL, **res = NULL;
4025 	void **wdata;
4026 
4027 	wdata = run_create_(name_code, &tdata, sizeof(tdata), script_cmds);
4028 	run_query(wdata);
4029 	dt = GET_DDATA(wdata);
4030 	txt = slurp_file(dt->name, 0);
4031 	run_destroy(wdata);
4032 	if (!txt)
4033 	{
4034 		alert_box(_("Error"), _("Unable to load script"), NULL);
4035 		return;
4036 	}
4037 
4038 	/* Stepping through arguments is done within run_create_(), so here
4039 	 * script_cmds is done with and free for overloading - WJ */
4040 	if (!cmd_mode) tx2 = gtkuncpy(NULL, txt, 0); // Files are in system encoding
4041 	res = wj_parse_argv(tx2 ? tx2 : txt);
4042 	run_script(res);
4043 	free(res);
4044 	free(tx2);
4045 	free(txt);
4046 }
4047 
do_script(int what)4048 static void do_script(int what)
4049 {
4050 	void **where = what == TOOLBAR_LAYERS ? layers_dock :
4051 		what == TOOLBAR_SETTINGS ? settings_dock :
4052 		/* what == TOOLBAR_TOOLS */ toolbar_boxes[TOOLBAR_TOOLS];
4053 	cmd_run_script(where, script_cmds);
4054 }
4055 
4056 /* Script mode image info */
4057 typedef struct {
4058 	char *str;
4059 	int dest;
4060 } sinf_dd;
4061 
4062 static char *info_dest[] = { "stdout", "standard output",
4063 	"stderr", "standard error", "clipboard" };
4064 #define DEST_STDERR 2
4065 #define DEST_CLIP 4
4066 
4067 #define WBbase sinf_dd
4068 static void *sinf_code[] = {
4069 	TOPVBOX,
4070 	XENTRY(str),
4071 	UNLESSv(cmd_mode), OPT(info_dest, DEST_CLIP + 1, dest),
4072 	IFv(cmd_mode), OPT(info_dest, DEST_CLIP, dest),
4073 		FLATTEN,
4074 	WSHOW
4075 };
4076 #undef WBbase
4077 
interpolate_info()4078 static void interpolate_info()
4079 {
4080 	static sinf_dd tdata = { "", 0 };
4081 	sinf_dd *dt;
4082 	void **res;
4083 
4084 	if (!script_cmds) return;
4085 	res = run_create_(sinf_code, &tdata, sizeof(tdata), script_cmds);
4086 	run_query(res);
4087 	dt = GET_DDATA(res);
4088 	if (dt->str[0]) // Do nothing for empty string
4089 	{
4090 		char *s = interpolate_line(dt->str, FALSE);
4091 		if (!s); // Do nothing if could not interpolate
4092 		else if (dt->dest < DEST_CLIP)
4093 		{
4094 			FILE *f = dt->dest < DEST_STDERR ? stdout : stderr;
4095 			fputs(s, f);
4096 			fflush(f); // In case no LF at end of line
4097 		}
4098 		else cmd_setv(((main_dd *)GET_DDATA(main_window_))->clipboard,
4099 			s, CLIP_TEXT);
4100 		free(s);
4101 	}
4102 	run_destroy(res);
4103 }
4104 
4105 /* Script mode color picker */
pick_pixel(multi_ext * mx,void ** where,int ab)4106 static int pick_pixel(multi_ext *mx, void **where, int ab)
4107 {
4108 	int area[4], *row = mx->rows[0] + 1;
4109 
4110 	/* Sanity check */
4111 	if ((mx->mincols < 2) || (mx->fractcol >= 0) || (mx->nrows > 2))
4112 		return (0);
4113 
4114 	/* 2 corners */
4115 	if ((mx->nrows == 2) && (mx->ncols == 2))
4116 	{
4117 		area[0] = row[0];
4118 		area[1] = row[1];
4119 		row = mx->rows[1] + 1;
4120 		area[2] = row[0] + 1;
4121 		area[3] = row[1] + 1;
4122 	}
4123 	/* 1 point */
4124 	else if (mx->ncols == 2)
4125 	{
4126 		area[2] = (area[0] = row[0]) + 1;
4127 		area[3] = (area[1] = row[1]) + 1;
4128 	}
4129 	/* Area with size */
4130 	else if (mx->ncols == 4)
4131 	{
4132 		area[2] = (area[0] = row[0]) + row[2];
4133 		area[3] = (area[1] = row[1]) + row[3];
4134 	}
4135 	/* Error */
4136 	else return (0);
4137 
4138 	/* Drop previous regular value */
4139 	cmd_set(origin_slot(where), -1);
4140 
4141 	if (clip(area, 0, 0, mem_width, mem_height, area))
4142 	{
4143 		int sz = (area[2] -= area[0]) | (area[3] -= area[1]);
4144 		pick_color(area[0], area[1], ab, sz != 1 ? area : NULL, -1);
4145 	}
4146 	return (1);
4147 }
4148 
4149 typedef struct {
4150 	int n, mode;
4151 } idx_dd;
4152 
idx_multi_evt(idx_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4153 static int idx_multi_evt(idx_dd *dt, void **wdata, int what, void **where,
4154 	multi_ext *mx)
4155 {
4156 	/* A/B mode */
4157 	if (dt->mode < 0x100) return (pick_pixel(mx, where, dt->mode));
4158 	/* Unmask/mask mode */
4159 	if ((mx->fractcol >= 0) || (mx->nrows > 1)) return (0);  // Error
4160 	cmd_set(origin_slot(where), 256); // Drop previous regular value
4161 	mem_mask_setv(mx->rows[0] + 1, mx->rows[0][0], dt->mode - 0x100);
4162 	return (1);
4163 }
4164 
4165 #define WBbase idx_dd
4166 static void *idx_code[] = {
4167 	TOPVBOX, SPIN(n, -1, 256), EVENT(MULTI, idx_multi_evt), WSHOW
4168 };
4169 #undef WBbase
4170 
script_idx(int mode)4171 static int script_idx(int mode)
4172 {
4173 	idx_dd tdata;
4174 	void **res;
4175 	int n;
4176 
4177 	if (!script_cmds) return (-1);
4178 	tdata.n = -1;
4179 	tdata.mode = mode;
4180 	res = run_create_(idx_code, &tdata, sizeof(tdata), script_cmds);
4181 	run_query(res);
4182 	n = ((idx_dd *)GET_DDATA(res))->n;
4183 	run_destroy(res);
4184 	return (n);
4185 }
4186 
4187 typedef struct {
4188 	int a, b;
4189 } ab_dd;
4190 
ab_pick_pixel(ab_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4191 static int ab_pick_pixel(ab_dd *dt, void **wdata, int what, void **where,
4192 	multi_ext *mx)
4193 {
4194 	return (pick_pixel(mx, where, cmd_read(where, dt) == &dt->b));
4195 }
4196 
4197 #define WBbase ab_dd
4198 static void *ab_code[] = {
4199 	TOPVBOX,
4200 	SPIN(a, -1, 256), ALTNAME("A"), EVENT(MULTI, ab_pick_pixel),
4201 	SPIN(b, -1, 256), OPNAME("B"), EVENT(MULTI, ab_pick_pixel),
4202 	WSHOW
4203 };
4204 #undef WBbase
4205 
script_ab()4206 static void script_ab()
4207 {
4208 	static ab_dd tdata = { -1, -1 };
4209 	ab_dd *dt;
4210 	void **res;
4211 
4212 	res = run_create_(ab_code, &tdata, sizeof(tdata), script_cmds);
4213 	run_query(res);
4214 	dt = GET_DDATA(res);
4215 	if ((dt->a >= 0) && (dt->a < mem_cols))
4216 		mem_col_A24 = mem_pal[mem_col_A = dt->a];
4217 	if ((dt->b >= 0) && (dt->b < mem_cols))
4218 		mem_col_B24 = mem_pal[mem_col_B = dt->b];
4219 	run_destroy(res);
4220 	update_stuff(UPD_AB);
4221 }
4222 
4223 typedef struct {
4224 	int x[3], y[3], n[3], b[3], brush;
4225 	void **nspin;
4226 } nxy_dd;
4227 
bp_evt(nxy_dd * dt,void ** wdata,int what,void ** where)4228 static void bp_evt(nxy_dd *dt, void **wdata, int what, void **where)
4229 {
4230 	void *var = cmd_read(where, dt);
4231 	int w = dt->x[2] + 1;
4232 
4233 	if (var == dt->n)
4234 	{
4235 		int n = dt->n[0];
4236 		dt->x[0] = n % w;
4237 		dt->y[0] = n / w;
4238 		if (dt->brush) mem_set_brush(n);
4239 		else mem_tool_pat = n;
4240 	}
4241 	else if (var == dt->b) mem_tool_pat_B = dt->b[0];
4242 	else if ((what == op_EVT_SCRIPT) && (dt->x[0] >= 0) && (dt->y[0] >= 0))
4243 		cmd_set(dt->nspin, dt->y[0] * w + dt->x[0]);
4244 }
4245 
bp_pick(nxy_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4246 static int bp_pick(nxy_dd *dt, void **wdata, int what, void **where,
4247 	multi_ext *mx)
4248 {
4249 	/* Sanity check */
4250 	if (mx->fractcol >= 0) return (0);  // Error
4251 
4252 	/* Grid position */
4253 	if ((mx->nrows == 1) && (mx->ncols == 2))
4254 	{
4255 		int *row = mx->rows[0] + 1, x = row[0], y = row[1];
4256 		cmd_set(where, bounded(y, 0, dt->y[2]) * (dt->x[2] + 1) +
4257 			bounded(x, 0, dt->x[2]));
4258 		return (1);
4259 	}
4260 
4261 	return (0); // Error
4262 }
4263 
4264 static char *brush_txt[TOOL_SPRAY + 1] = { "Square", "Circle",
4265 	"Horizontal", "Vertical", "Slash", "Backslash", "Spray" };
4266 
4267 #define WBbase nxy_dd
4268 static void *bp_code[] = {
4269 	TOPVBOX,
4270 	REF(nspin), SPINa(n), EVENT(CHANGE, bp_evt), EVENT(MULTI, bp_pick),
4271 	UNLESSx(brush, 1), ALTNAME("A"), IFvx(pattern_B, 1), /* && */
4272 		SPINa(b), EVENT(CHANGE, bp_evt), EVENT(MULTI, bp_pick), OPNAME("B"),
4273 	ENDIF(1),
4274 	SPINa(x), EVENT(SCRIPT, bp_evt), OPNAME("X"),
4275 	SPINa(y), EVENT(SCRIPT, bp_evt), OPNAME("Y"),
4276 	IFx(brush, 1),
4277 		RPACKv(brush_txt, TOOL_SPRAY + 1, 1, brush_type),
4278 			 EVENT(SELECT, bp_evt), OPNAME("Type"),
4279 		SPINv(tool_size, 1, 255), EVENT(CHANGE, bp_evt), OPNAME("Size"),
4280 		SPINv(tool_flow, 1, 255), EVENT(CHANGE, bp_evt), OPNAME("Flow"),
4281 	ENDIF(1),
4282 	WSHOW
4283 };
4284 #undef WBbase
4285 
script_bp(int mode)4286 static void script_bp(int mode)
4287 {
4288 	nxy_dd tdata;
4289 
4290 	memset(&tdata, 0, sizeof(tdata));
4291 	tdata.x[1] = tdata.y[1] = -1;
4292 	if (mode == CHOOSE_BRUSH)
4293 	{
4294 		tdata.x[2] = BRUSH_GRID_W - 1;
4295 		tdata.y[2] = BRUSH_GRID_H - 1;
4296 		tdata.x[0] = tdata.y[0] = -1;
4297 		tdata.brush = TRUE;
4298 	}
4299 	else
4300 	{
4301 		tdata.x[2] = patterns_grid_w - 1;
4302 		tdata.y[2] = patterns_grid_h - 1;
4303 		tdata.n[0] = mem_tool_pat;
4304 		tdata.b[0] = mem_tool_pat_B;
4305 		tdata.x[0] = mem_tool_pat % patterns_grid_w;
4306 		tdata.y[0] = mem_tool_pat / patterns_grid_w;
4307 		tdata.n[1] = tdata.b[1] = -DEF_PATTERNS;
4308 	}
4309 	tdata.n[2] = tdata.b[2] = (tdata.x[2] + 1) * (tdata.y[2] + 1) - 1;
4310 	run_destroy(run_create_(bp_code, &tdata, sizeof(tdata), script_cmds));
4311 	if (mode == CHOOSE_BRUSH)
4312 	{
4313 		change_to_tool(TTB_PAINT);
4314 		update_stuff(UPD_BRUSH);
4315 	}
4316 	else update_stuff(UPD_PAT);
4317 }
4318 
4319 typedef struct {
4320 	int w, h, rxy[4];
4321 	multi_ext *mx;
4322 	void **group;
4323 } rect_dd;
4324 
make_select(rect_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4325 static int make_select(rect_dd *dt, void **wdata, int what, void **where,
4326 	multi_ext *mx)
4327 {
4328 	/* Drop previous polygon if any */
4329 	free(dt->mx);
4330 	dt->mx = NULL;
4331 
4332 	/* Sanity check */
4333 	if ((mx->mincols < 2) || (mx->fractcol >= 0)) return (0);
4334 	if ((mx->ncols != 2) && ((mx->nrows != 1) || (mx->ncols != 4)))
4335 		return (0);
4336 
4337 	/* Point/rectangle selection */
4338 	if (mx->nrows <= 2)
4339 	{
4340 		int *row = mx->rows[0] + 1;
4341 		dt->rxy[0] = row[0];
4342 		dt->rxy[1] = row[1];
4343 		if (mx->nrows > 1) row = mx->rows[1] + 1;
4344 		dt->rxy[2] = row[0];
4345 		dt->rxy[3] = row[1];
4346 		if (mx->ncols > 2)
4347 		{
4348 			dt->rxy[2] += row[2] - 1;
4349 			dt->rxy[3] += row[3] - 1;
4350 		}
4351 		cmd_reset(dt->group, dt); // Update widgets
4352 		return (1);
4353 	}
4354 
4355 	/* Polygon selection */
4356 	dt->mx = mx;
4357 	return (-1); // Keep the data
4358 }
4359 
4360 #define WBbase rect_dd
4361 static void *rect_code[] = {
4362 	TOPVBOX, EVENT(MULTI, make_select), OPNAME(""),
4363 	REF(group), GROUPR,
4364 	SPIN(rxy[0], 0, MAX_WIDTH - 1), OPNAME("x0"),
4365 	SPIN(rxy[1], 0, MAX_HEIGHT - 1), OPNAME("y0"),
4366 	SPIN(rxy[2], 0, MAX_WIDTH - 1), OPNAME("x1"),
4367 	SPIN(rxy[3], 0, MAX_HEIGHT - 1), OPNAME("y1"),
4368 	SPIN(w, 0, MAX_WIDTH), OPNAME("width"),
4369 	SPIN(h, 0, MAX_HEIGHT), OPNAME("height"),
4370 	WSHOW
4371 };
4372 #undef WBbase
4373 
script_rect(int * rect)4374 static multi_ext *script_rect(int *rect)
4375 {
4376 	static rect_dd tdata = { 0, 0, { 0, 0, MAX_WIDTH - 1, MAX_HEIGHT - 1 } };
4377 	rect_dd *dt;
4378 	multi_ext *mx;
4379 	void **res;
4380 
4381 	copy4(rect, tdata.rxy);
4382 	if (!script_cmds) return (NULL);
4383 
4384 	res = run_create_(rect_code, &tdata, sizeof(tdata), script_cmds);
4385 	run_query(res);
4386 	dt = GET_DDATA(res);
4387 	mx = dt->mx;
4388 	if (dt->w) dt->rxy[2] = dt->rxy[0] + dt->w - 1;
4389 	if (dt->h) dt->rxy[3] = dt->rxy[1] + dt->h - 1;
4390 	copy4(rect, dt->rxy);
4391 	run_destroy(res);
4392 	return (mx);
4393 }
4394 
4395 typedef struct {
4396 	int swap, mx, dx, dy, align, x, y;
4397 } paste_dd;
4398 
4399 static char *paste_align[] = { "Left", "Right", "Top", "Bottom", "Centre" };
4400 
align_evt(paste_dd * dt,void ** wdata,int what,void ** where)4401 static void align_evt(paste_dd *dt, void **wdata, int what, void **where)
4402 {
4403 	cmd_read(where, dt);
4404 	switch (dt->align)
4405 	{
4406 	case 0: // Left
4407 		dt->dx = 0; break;
4408 	case 1: // Right
4409 		dt->dx = mem_clip_w - 1; break;
4410 	case 2: // Top
4411 		dt->dy = 0; break;
4412 	case 3: // Bottom
4413 		dt->dy = mem_clip_h - 1; break;
4414 	case 4: // Center
4415 		dt->dx = mem_clip_w / 2; dt->dy = mem_clip_h / 2; break;
4416 	}
4417 }
4418 
do_paste(paste_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4419 static int do_paste(paste_dd *dt, void **wdata, int what, void **where,
4420 	multi_ext *mx)
4421 {
4422 	int i, fr, mode, rows, dx = dt->dx, dy = dt->dy, p = MAX_PRESSURE;
4423 
4424 	/* Sanity check */
4425 	if ((mx->ncols > 3) || (mx->mincols < 2) ||
4426 		((mx->fractcol >= 0) && (mx->fractcol != 2))) return (0); // Error
4427 
4428 	do_tool_action(TC_PASTE_DRAG, marq_x1, marq_y1, 0);
4429 
4430 	mode = dt->swap ? TC_PASTE_PSWAP | TCF_PRES | TCF_ONCE :
4431 		TC_PASTE_PAINT | TCF_PRES | TCF_ONCE;
4432 	fr = mx->fractcol == 2;
4433 	rows = mx->nrows;
4434 	for (i = 0; i < rows; i++)
4435 	{
4436 		int *row = mx->rows[i];
4437 		if (row[0] > 2) p = bounded(fr ? row[3] :
4438 			(row[3] * MAX_PRESSURE) / 100, 0, MAX_PRESSURE);
4439 		do_tool_action(mode, row[1] - dx, row[2] - dy, p);
4440 	}
4441 
4442 	do_tool_action(TC_SEL_STOP, marq_x1, marq_y1, 0);
4443 	tool_done();
4444 	dt->mx = TRUE;
4445 	return (1);
4446 }
4447 
4448 #define WBbase paste_dd
4449 static void *paste_code[] = {
4450 	TOPVBOX,
4451 	OPT(paste_align, 5, align), EVENT(SCRIPT, align_evt),
4452 		ALTNAME("Align"), FLATTEN, EVENT(MULTI, do_paste),
4453 	CHECK("swap", swap), EVENT(CHANGE, align_evt),
4454 	SPIN(x, -MAX_WIDTH, MAX_WIDTH), OPNAME("x0"),
4455 	SPIN(y, -MAX_HEIGHT, MAX_HEIGHT), OPNAME("y0"),
4456 	WSHOW
4457 };
4458 #undef WBbase
4459 
script_paste(int centre)4460 void script_paste(int centre)
4461 {
4462 	paste_dd tdata, *dt;
4463 	void **res;
4464 
4465 	memset(&tdata, 0, sizeof(tdata));
4466 	tdata.x = marq_x1;
4467 	tdata.y = marq_y1;
4468 	if (centre)
4469 	{
4470 		tdata.x += tdata.dx = mem_clip_w / 2;
4471 		tdata.y += tdata.dy = mem_clip_h / 2;
4472 		tdata.align = 4;
4473 	}
4474 	res = run_create_(paste_code, &tdata, sizeof(tdata), script_cmds);
4475 	run_query(res);
4476 	dt = GET_DDATA(res);
4477 	if (!dt->mx) // No commits yet - paste at x & y
4478 	{
4479 		int x = dt->x - dt->dx, y = dt->y - dt->dy;
4480 		if ((marq_x1 ^ x) | (marq_y1 ^ y)) // Moved
4481 			paint_marquee(MARQ_MOVE, x, y, NULL);
4482 		commit_paste(dt->swap, NULL);
4483 		tool_done();
4484 	}
4485 	run_destroy(res);
4486 	update_stuff(UPD_SGEOM);
4487 }
4488 
4489 typedef struct {
4490 	int n[3];
4491 } inrange_dd;
4492 
4493 #define WBbase inrange_dd
4494 static void *inrange_code[] = {
4495 	TOPVBOX, SPINa(n), WSHOW
4496 };
4497 #undef WBbase
4498 
script_inrange(int n0,int nmin,int nmax)4499 static int script_inrange(int n0, int nmin, int nmax)
4500 {
4501 	if (script_cmds)
4502 	{
4503 		inrange_dd tdata = { { n0, nmin, nmax } };
4504 		void **res = run_create_(inrange_code, &tdata, sizeof(tdata), script_cmds);
4505 		run_query(res);
4506 		n0 = ((inrange_dd *)GET_DDATA(res))->n[0];
4507 		run_destroy(res);
4508 	}
4509 	return (n0);
4510 }
4511 
do_act_esc()4512 static void do_act_esc()
4513 {
4514 	if ((tool_type == TOOL_SELECT) || (tool_type == TOOL_POLYGON))
4515 		pressed_select(FALSE);
4516 	else if (tool_type == TOOL_LINE)
4517 	{
4518 		stop_line();
4519 		update_sel_bar(FALSE);
4520 	}
4521 	else if ((tool_type == TOOL_GRADIENT) &&
4522 		(gradient[mem_channel].status != GRAD_NONE))
4523 	{
4524 		do_grad_action(TC_GRAD_CLEAR, 0, 0);
4525 		update_sel_bar(FALSE);
4526 	}
4527 }
4528 
select_poly(multi_ext * mx)4529 static void select_poly(multi_ext *mx)
4530 {
4531 	int i, rows = mx->nrows;
4532 
4533 	for (i = 0; i < rows; i++)
4534 	{
4535 		int *row = mx->rows[i] + 1;
4536 		do_tool_action(i ? TC_POLY_ADD | TCF_ONCE : TC_POLY_START,
4537 			bounded(row[0], 0, mem_width - 1),
4538 			bounded(row[1], 0, mem_height - 1), 0);
4539 	}
4540 	do_tool_action(TC_POLY_CLOSE, 0, 0, 0);
4541 }
4542 
tool_command(main_dd * dt,void ** wdata,int what,void ** where,multi_ext * mx)4543 static int tool_command(main_dd *dt, void **wdata, int what, void **where,
4544 	multi_ext *mx)
4545 {
4546 	int *row;
4547 	int i, p, fr, rows = mx->nrows, res = 0;
4548 
4549 	do_act_esc();
4550 	switch (tool_type)
4551 	{
4552 	case TOOL_GRADIENT:
4553 		if ((rows == 1) && (mx->ncols < 3)) res = 1; // Valid clear op
4554 		if ((rows != 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4555 			(mx->fractcol >= 0)) break; // Error
4556 		row = mx->rows[0] + 1;
4557 		do_grad_action(TC_GRAD_START,
4558 			bounded(row[0], -MAX_WIDTH, MAX_WIDTH),
4559 			bounded(row[1], -MAX_HEIGHT, MAX_HEIGHT));
4560 		row = mx->rows[1] + 1;
4561 		do_grad_action(TC_GRAD_SET1,
4562 			bounded(row[0], -MAX_WIDTH, MAX_WIDTH),
4563 			bounded(row[1], -MAX_HEIGHT, MAX_HEIGHT));
4564 		res = 1;
4565 		break;
4566 	case TOOL_LINE:
4567 		if ((rows < 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4568 			(mx->fractcol >= 0)) break; // Error
4569 		for (i = 0; i < rows; i++)
4570 		{
4571 			row = mx->rows[i] + 1;
4572 			do_tool_action(TC_LINE_START,
4573 				bounded(row[0], 0, mem_width - 1),
4574 				bounded(row[1], 0, mem_height - 1), MAX_PRESSURE);
4575 			line_status = LINE_LINE;
4576 		}
4577 		stop_line();
4578 		res = 1;
4579 		break;
4580 	case TOOL_SELECT:
4581 		if ((rows > 2) || (mx->ncols != 2) || (mx->mincols != 2) ||
4582 			(mx->fractcol >= 0)) break; // Error
4583 		row = mx->rows[0] + 1;
4584 		do_tool_action(TC_SEL_START,
4585 				bounded(row[0], 0, mem_width - 1),
4586 				bounded(row[1], 0, mem_height - 1), 0);
4587 		if (rows > 1) row = mx->rows[1] + 1;
4588 		do_tool_action(TC_SEL_TO,
4589 				bounded(row[0], 0, mem_width - 1),
4590 				bounded(row[1], 0, mem_height - 1), 0);
4591 		do_tool_action(TC_SEL_STOP, 0, 0, 0);
4592 		res = 1;
4593 		break;
4594 	case TOOL_POLYGON:
4595 		if ((mx->ncols != 2) || (mx->mincols != 2) ||
4596 			(mx->fractcol >= 0)) break; // Error
4597 		select_poly(mx);
4598 		res = 1;
4599 		break;
4600 	default: /* Regular painting tools */
4601 		if ((mx->ncols > 3) || (mx->mincols < 2) ||
4602 			((mx->fractcol >= 0) && (mx->fractcol != 2))) break; // Error
4603 		fr = mx->fractcol == 2;
4604 		p = MAX_PRESSURE;
4605 		for (i = 0; i < rows; i++)
4606 		{
4607 			row = mx->rows[i];
4608 			if (row[0] > 2) p = bounded(fr ? row[3] :
4609 				(row[3] * MAX_PRESSURE) / 100, 0, MAX_PRESSURE);
4610 			do_tool_action(TC_PAINT | TCF_PRES | TCF_ONCE,
4611 				bounded(row[1], 0, mem_width - 1),
4612 				bounded(row[2], 0, mem_height - 1), p);
4613 		}
4614 		res = 1;
4615 		break;
4616 	}
4617 
4618 	if (res) /* Do as for button release */
4619 	{
4620 		tool_done();
4621 		update_menus();
4622 	}
4623 	return (res);
4624 }
4625 
4626 void *scriptbar_code[] = {
4627 	EVENT(MULTI, tool_command), ALTNAME(""), // for tools toolbar
4628 	RET
4629 };
4630 
new_zoom(int mode,float zoom)4631 static float new_zoom(int mode, float zoom)
4632 {
4633 	return (!mode ? // Zoom in
4634 		(zoom >= 1 ? zoom + 1 : 1.0 / (rint(1.0 / zoom) - 1)) :
4635 		mode == -1 ? // Zoom out
4636 		(zoom > 1 ? zoom - 1 : 1.0 / (rint(1.0 / zoom) + 1)) :
4637 		mode > 0 ? mode : -1.0 / mode); // Zoom to given factor/divisor
4638 }
4639 
action_dispatch(int action,int mode,int state,int kbd)4640 void action_dispatch(int action, int mode, int state, int kbd)
4641 {
4642 	int change = mode & 1 ? mem_nudge : 1, dir = (mode >> 1) - 1;
4643 
4644 	switch (action)
4645 	{
4646 	case ACT_QUIT:
4647 		quit_all(mode); break;
4648 	case ACT_ZOOM:
4649 		align_size(new_zoom(mode, can_zoom));
4650 		break;
4651 	case ACT_VIEW:
4652 		toggle_view(); break;
4653 	case ACT_PAN:
4654 		pressed_pan(); break;
4655 	case ACT_CROP:
4656 		pressed_crop(); break;
4657 	case ACT_SWAP_AB:
4658 		mem_swap_cols(TRUE); break;
4659 	case ACT_TOOL:
4660 		if (state || kbd) change_to_tool(mode); // Ignore DEactivating buttons
4661 		break;
4662 	case ACT_SEL_MOVE:
4663 		/* Gradient tool has precedence over selection */
4664 		if ((tool_type != TOOL_GRADIENT) && (marq_status > MARQUEE_NONE))
4665 			move_marquee(MARQ_MOVE, marq_xy, change, dir);
4666 		else move_mouse(change * arrow_dx[dir], change * arrow_dy[dir], 0);
4667 		break;
4668 	case ACT_OPAC:
4669 		pressed_opacity(mode > 0 ? (255 * mode) / 10 :
4670 			tool_opacity + 1 + mode + mode);
4671 		break;
4672 	case ACT_LR_MOVE:
4673 		/* User is selecting so allow CTRL+arrow keys to resize the
4674 		 * marquee; for consistency, gradient tool blocks this */
4675 		if ((tool_type != TOOL_GRADIENT) && (marq_status == MARQUEE_DONE))
4676 			move_marquee(MARQ_SIZE, marq_xy + 2, change, dir);
4677 		else if (bkg_flag && !layer_selected)
4678 		{
4679 			/* !!! Later, maybe localize redraw to the changed part */
4680 			bkg_x += change * arrow_dx[dir];
4681 			bkg_y += change * arrow_dy[dir];
4682 			update_stuff(UPD_RENDER);
4683 		}
4684 		else if (layers_total) move_layer_relative(layer_selected,
4685 			change * arrow_dx[dir], change * arrow_dy[dir]);
4686 		break;
4687 	case ACT_ESC: do_act_esc(); break;
4688 	case ACT_COMMIT:
4689 		if (marq_status >= MARQUEE_PASTE)
4690 		{
4691 			commit_paste(mode, NULL);
4692 			pen_down = 0;	// Ensure each press of enter is a new undo level
4693 			mem_undo_prepare();
4694 		}
4695 		else move_mouse(0, 0, 1);
4696 		break;
4697 	case ACT_RCLICK:
4698 		if (marq_status < MARQUEE_PASTE) move_mouse(0, 0, 3);
4699 		break;
4700 	case ACT_ARROW: draw_arrow(mode); break;
4701 	case ACT_A:
4702 	case ACT_B:
4703 		action = action == ACT_B;
4704 		dir = script_idx(action);
4705 		if (!mode && (dir < 0)); // Nothing to do
4706 		else if (mem_channel == CHN_IMAGE)
4707 		{
4708 			mode = mode ? mode + mem_col_[action] : dir;
4709 			if ((mode >= 0) && (mode < mem_cols))
4710 				mem_col_[action] = mode;
4711 			mem_col_24[action] = mem_pal[mem_col_[action]];
4712 		}
4713 		else
4714 		{
4715 			mode = mode ? mode + channel_col_[action][mem_channel] : dir;
4716 			if ((mode >= 0) && (mode <= 255))
4717 				channel_col_[action][mem_channel] = mode;
4718 		}
4719 		update_stuff(UPD_CAB);
4720 		break;
4721 	case ACT_SIZE:
4722 		tool_size = bounded(tool_size + mode, 1, 255);
4723 		update_stuff(UPD_BRUSH);
4724 		break;
4725 	case ACT_PAT:
4726 		mem_tool_pat = bounded(mem_tool_pat + mode, -DEF_PATTERNS,
4727 			patterns_grid_w * patterns_grid_h - 1);
4728 		update_stuff(UPD_PAT);
4729 		break;
4730 	case ACT_CHANNEL:
4731 		if (kbd) state = TRUE;
4732 		if (mode < 0) pressed_channel_create(mode);
4733 		else pressed_channel_edit(state, mode);
4734 		break;
4735 	case ACT_VWZOOM:
4736 		vw_align_size(new_zoom(mode, vw_zoom));
4737 		break;
4738 	case ACT_SAVE:
4739 		pressed_save_file(); break;
4740 	case ACT_FACTION:
4741 		pressed_file_action(mode); break;
4742 	case ACT_LOAD_RECENT:
4743 		pressed_load_recent(mode); break;
4744 	case ACT_DO_UNDO:
4745 		pressed_do_undo(mode, script_inrange(1, 1,
4746 			mode ? mem_undo_redo : mem_undo_done));
4747 		break;
4748 	case ACT_COPY:
4749 		pressed_copy(mode); break;
4750 	case ACT_PASTE:
4751 		pressed_paste(mode);
4752 		if (script_cmds && (marq_status >= MARQUEE_PASTE))
4753 			script_paste(mode); // Move and commit
4754 		break;
4755 	case ACT_COPY_PAL:
4756 		pressed_pal_copy(); break;
4757 	case ACT_PASTE_PAL:
4758 		pressed_pal_paste(); break;
4759 	case ACT_LOAD_CLIP:
4760 		load_clip(mode); break;
4761 	case ACT_SAVE_CLIP:
4762 		save_clip(mode); break;
4763 	case ACT_TBAR:
4764 		pressed_toolbar_toggle(state, mode); break;
4765 	case ACT_DOCK:
4766 		if (!kbd) toggle_dock(show_dock = state);
4767 		else if (cmd_checkv(menu_slots[MENU_DOCK], SLOT_SENSITIVE))
4768 			cmd_set(menu_slots[MENU_DOCK], !show_dock);
4769 		break;
4770 	case ACT_CENTER:
4771 		pressed_centralize(state); break;
4772 	case ACT_GRID:
4773 		zoom_grid(state); break;
4774 	case ACT_SNAP:
4775 		tgrid_snap = state;
4776 		break;
4777 	case ACT_VWWIN:
4778 		if (state) view_show();
4779 		else view_hide();
4780 		break;
4781 	case ACT_VWSPLIT:
4782 		pressed_view_hori(state); break;
4783 	case ACT_VWFOCUS:
4784 		pressed_view_focus(state); break;
4785 	case ACT_FLIP_V:
4786 		pressed_flip_image_v(); break;
4787 	case ACT_FLIP_H:
4788 		pressed_flip_image_h(); break;
4789 	case ACT_ROTATE:
4790 		pressed_rotate_image(mode); break;
4791 	case ACT_SELECT:
4792 		pressed_select(mode); break;
4793 	case ACT_LASSO:
4794 		pressed_lasso(mode); break;
4795 	case ACT_OUTLINE:
4796 		pressed_rectangle(mode); break;
4797 	case ACT_ELLIPSE:
4798 		pressed_ellipse(mode); break;
4799 	case ACT_SEL_FLIP_V:
4800 		pressed_flip_sel_v(); break;
4801 	case ACT_SEL_FLIP_H:
4802 		pressed_flip_sel_h(); break;
4803 	case ACT_SEL_ROT:
4804 		pressed_rotate_sel(mode); break;
4805 	case ACT_RAMP:
4806 		pressed_sel_ramp(mode); break;
4807 	case ACT_SEL_ALPHA_AB:
4808 		pressed_clip_alpha_scale(); break;
4809 	case ACT_SEL_ALPHAMASK:
4810 		pressed_clip_alphamask(); break;
4811 	case ACT_SEL_MASK_AB:
4812 		pressed_clip_mask(mode); break;
4813 	case ACT_SEL_MASK:
4814 		if (!mode) pressed_clip_mask_all();
4815 		else pressed_clip_mask_clear();
4816 		break;
4817 	case ACT_PAL_DEF:
4818 		pressed_default_pal(); break;
4819 	case ACT_PAL_MASK:
4820 		mem_mask_setv(NULL, script_idx(mode + 0x100), mode);
4821 		update_stuff(UPD_CMASK);
4822 		break;
4823 	case ACT_DITHER_A:
4824 		pressed_dither_A(); break;
4825 	case ACT_PAL_MERGE:
4826 		pressed_remove_duplicates(); break;
4827 	case ACT_PAL_CLEAN:
4828 		pressed_remove_unused(); break;
4829 	case ACT_ISOMETRY:
4830 		iso_trans(mode); break;
4831 	case ACT_CHN_DIS:
4832 		pressed_channel_disable(state, mode); break;
4833 	case ACT_SET_RGBA:
4834 		pressed_RGBA_toggle(state); break;
4835 	case ACT_SET_OVERLAY:
4836 		pressed_channel_toggle(state, mode); break;
4837 	case ACT_LR_SAVE:
4838 		layer_press_save(); break;
4839 	case ACT_LR_ADD:
4840 		if (mode == LR_NEW) generic_new_window(1);
4841 		else if (mode == LR_DUP) layer_press_duplicate();
4842 		else if (mode == LR_PASTE) pressed_paste_layer();
4843 		else /* if (mode == LR_COMP) */ layer_add_composite();
4844 		break;
4845 	case ACT_LR_DEL:
4846 		if (!mode) layer_press_delete();
4847 		else layer_press_remove_all();
4848 		break;
4849 	case ACT_DOCS:
4850 		show_html(inifile_get(HANDBOOK_BROWSER_INI, NULL),
4851 			inifile_get(HANDBOOK_LOCATION_INI, NULL));
4852 		break;
4853 	case ACT_REBIND_KEYS:
4854 		/* "Tool of last resort" for when shortcuts don't work */
4855 		cmd_reset(main_keys, NULL); break;
4856 	case ACT_MODE:
4857 		mode_change(mode, state); break;
4858 	case ACT_LR_SHIFT:
4859 		shift_layer(mode); break;
4860 	case ACT_LR_CENTER:
4861 		layer_press_centre(); break;
4862 	case ACT_SCRIPT:
4863 		do_script(mode); break;
4864 	case ACT_RUN_SCRIPT:
4865 		if (!mode) load_script();
4866 		else launch_script(mode);
4867 		break;
4868 	case DLG_BRCOSA:
4869 		pressed_brcosa(NULL); break;
4870 	case DLG_CHOOSER:
4871 		if (!script_cmds) choose_pattern(mode);
4872 		else if (mode == CHOOSE_COLOR) script_ab();
4873 		else script_bp(mode);
4874 		break;
4875 	case DLG_SCALE:
4876 		pressed_scale_size(TRUE); break;
4877 	case DLG_SIZE:
4878 		pressed_scale_size(FALSE); break;
4879 	case DLG_NEW:
4880 		generic_new_window(0); break;
4881 	case DLG_FSEL:
4882 		file_selector(mode); break;
4883 	case DLG_FACTIONS:
4884 		pressed_file_configure(); break;
4885 	case DLG_TEXT:
4886 		pressed_text(); break;
4887 	case DLG_TEXT_FT:
4888 		pressed_mt_text(); break;
4889 	case DLG_LAYERS:
4890 		if (mode) cmd_set(menu_slots[MENU_LAYER], FALSE); // Closed by toolbar
4891 		else if (state) pressed_layers();
4892 		else delete_layers_window();
4893 		break;
4894 	case DLG_INDEXED:
4895 		pressed_quantize(mode); break;
4896 	case DLG_ROTATE:
4897 		pressed_rotate_free(); break;
4898 	case DLG_INFO:
4899 		if (script_cmds) interpolate_info();
4900 		else pressed_information();
4901 		break;
4902 	case DLG_PREFS:
4903 		pressed_preferences(); break;
4904 	case DLG_COLORS:
4905 		colour_selector(mode); break;
4906 	case DLG_PAL_SIZE:
4907 		pressed_add_cols(); break;
4908 	case DLG_PAL_SORT:
4909 		pressed_sort_pal(); break;
4910 	case DLG_PAL_SHIFTER:
4911 		pressed_shifter(); break;
4912 	case DLG_CHN_DEL:
4913 		pressed_channel_delete(); break;
4914 	case DLG_ANI:
4915 		pressed_animate_window(); break;
4916 	case DLG_ANI_VIEW:
4917 		ani_but_preview(NULL); break;
4918 	case DLG_ANI_KEY:
4919 		pressed_set_key_frame(); break;
4920 	case DLG_ANI_KILLKEY:
4921 		pressed_remove_key_frames(); break;
4922 	case DLG_ABOUT:
4923 		pressed_help(); break;
4924 	case DLG_SKEW:
4925 		pressed_skew(); break;
4926 	case DLG_FLOOD:
4927 		flood_settings(); break;
4928 	case DLG_SMUDGE:
4929 		smudge_settings(); break;
4930 	case DLG_CLONE:
4931 		clone_settings(); break;
4932 	case DLG_GRAD:
4933 		gradient_setup(mode); break;
4934 	case DLG_STEP:
4935 		step_settings(); break;
4936 	case DLG_FILT:
4937 		blend_settings(); break;
4938 	case DLG_TRACE:
4939 		bkg_setup(); break;
4940 	case DLG_PICK_GRAD:
4941 		pressed_pick_gradient(); break;
4942 	case DLG_SEGMENT:
4943 		pressed_segment(); break;
4944 	case DLG_SCRIPT:
4945 		pressed_script(); break;
4946 	case DLG_LASSO:
4947 		lasso_settings(); break;
4948 	case DLG_KEYS:
4949 		keys_selector(); break;
4950 	case DLG_XHOLD:
4951 		pressed_xhold(); break;
4952 	case DLG_NOISE:
4953 		pressed_noise(); break;
4954 	case FILT_2RGB:
4955 		if (mem_img_bpp == 1) pressed_convert_rgb();
4956 		// Allow a noop in script mode
4957 		else if (script_cmds) spot_undo(UNDO_PAL);
4958 		break;
4959 	case FILT_INVERT:
4960 	case FILT_NORM:
4961 		pressed_inv(action); break;
4962 	case FILT_GREY:
4963 		pressed_greyscale(mode); break;
4964 	case FILT_EDGE:
4965 		pressed_edge_detect(); break;
4966 	case FILT_DOG:
4967 		pressed_dog(); break;
4968 	case FILT_SHARPEN:
4969 		pressed_sharpen(); break;
4970 	case FILT_UNSHARP:
4971 		pressed_unsharp(); break;
4972 	case FILT_SOFTEN:
4973 		pressed_soften(); break;
4974 	case FILT_GAUSS:
4975 		pressed_gauss(); break;
4976 	case FILT_FX:
4977 		pressed_fx(mode); break;
4978 	case FILT_BACT:
4979 		pressed_bacteria(); break;
4980 	case FILT_MAP:
4981 		pressed_map(); break;
4982 	case FILT_THRES:
4983 		pressed_threshold(); break;
4984 	case FILT_UALPHA:
4985 		pressed_unassociate(); break;
4986 	case FILT_KUWAHARA:
4987 		pressed_kuwahara(); break;
4988 	}
4989 }
4990 
menu_action(void * dt,void ** wdata,int what,void ** where)4991 static void menu_action(void *dt, void **wdata, int what, void **where)
4992 {
4993 	int act_m = TOOL_ID(where), res = TRUE;
4994 	void *cause = cmd_read(where, dt);
4995 	// Good for radioitems too, as all action codes are nonzero
4996 	if (cause) res = *(int *)cause;
4997 
4998 	action_dispatch(act_m >> 16, (act_m & 0xFFFF) - 0x8000, res, FALSE);
4999 }
5000 
do_pal_copy(png_color * tpal,unsigned char * img,unsigned char * alpha,unsigned char * mask,unsigned char * mask2,png_color * wpal,int w,int h,int bpp,int step)5001 static int do_pal_copy(png_color *tpal, unsigned char *img,
5002 	unsigned char *alpha, unsigned char *mask,
5003 	unsigned char *mask2, png_color *wpal,
5004 	int w, int h, int bpp, int step)
5005 {
5006 	int i, j, n;
5007 
5008 	mem_pal_copy(tpal, mem_pal);
5009 	for (n = i = 0; i < h; i++)
5010 	{
5011 		for (j = 0; j < w; j++ , img += bpp)
5012 		{
5013 			/* Skip empty parts */
5014 			if ((mask2 && !mask2[j]) || (mask && !mask[j]) ||
5015 				(alpha && !alpha[j])) continue;
5016 			if (bpp == 1) tpal[n] = wpal[*img];
5017 			else
5018 			{
5019 				tpal[n].red = img[0];
5020 				tpal[n].green = img[1];
5021 				tpal[n].blue = img[2];
5022 			}
5023 			if (++n >= 256) return (256);
5024 		}
5025 		img += (step - w) * bpp;
5026 		if (alpha) alpha += step;
5027 		if (mask) mask += step;
5028 		if (mask2) mask2 += w;
5029 	}
5030 	return (n);
5031 }
5032 
pressed_pal_copy()5033 static void pressed_pal_copy()
5034 {
5035 	png_color tpal[256];
5036 	int n = 0;
5037 
5038 	/* Source is selection */
5039 	if ((marq_status == MARQUEE_DONE) || (poly_status == POLY_DONE))
5040 	{
5041 		unsigned char *mask2 = NULL;
5042 		int i, bpp, rect[4];
5043 
5044 		marquee_at(rect);
5045 		if ((poly_status == POLY_DONE) &&
5046 			(mask2 = calloc(1, rect[2] * rect[3])))
5047 			poly_draw(TRUE, mask2, rect[2]);
5048 
5049 		i = rect[1] * mem_width + rect[0];
5050 		bpp = MEM_BPP;
5051 		n = do_pal_copy(tpal, mem_img[mem_channel] + i * bpp,
5052 			(mem_channel == CHN_IMAGE) && mem_img[CHN_ALPHA] ?
5053 			mem_img[CHN_ALPHA] + i : NULL,
5054 			(mem_channel <= CHN_ALPHA) && mem_img[CHN_SEL] ?
5055 			mem_img[CHN_SEL] + i : NULL,
5056 			mask2, mem_pal,
5057 			rect[2], rect[3], bpp, mem_width);
5058 
5059 		if (mask2) free(mask2);
5060 	}
5061 	/* Source is clipboard */
5062 	else if (mem_clipboard) n = do_pal_copy(tpal, mem_clipboard,
5063 		mem_clip_alpha, mem_clip_mask,
5064 		NULL, mem_clip_paletted ? mem_clip_pal : mem_pal,
5065 		mem_clip_w, mem_clip_h, mem_clip_bpp, mem_clip_w);
5066 
5067 	if (!n) return; // Empty set - ignore
5068 	spot_undo(UNDO_PAL);
5069 	mem_pal_copy(mem_pal, tpal);
5070 	mem_cols = n;
5071 	update_stuff(UPD_PAL);
5072 }
5073 
pressed_pal_paste()5074 static void pressed_pal_paste()
5075 {
5076 	unsigned char *dest;
5077 	int i, h, w = mem_cols;
5078 
5079 	// If selection exists, use it to set the width of the clipboard
5080 	if ((tool_type == TOOL_SELECT) && (marq_status == MARQUEE_DONE))
5081 	{
5082 		w = abs(marq_x1 - marq_x2) + 1;
5083 		if (w > mem_cols) w = mem_cols;
5084 	}
5085 	h = (mem_cols + w - 1) / w;
5086 
5087 	mem_clip_new(w, h, 1, CMASK_IMAGE, NULL);
5088 	if (!mem_clipboard)
5089 	{
5090 		memory_errors(1);
5091 		return;
5092 	}
5093 	mem_clip_paletted = 1;
5094 	mem_pal_copy(mem_clip_pal, mem_pal);
5095 	memset(dest = mem_clipboard, 0, w * h);
5096 	for (i = 0; i < mem_cols; i++) dest[i] = i;
5097 
5098 	pressed_paste(TRUE);
5099 }
5100 
5101 ///	DOCK AREA
5102 
toggle_dock(int state)5103 static void toggle_dock(int state)
5104 {
5105 	cmd_set(dock_area, state);
5106 }
5107 
pressed_sel_ramp(int vert)5108 static void pressed_sel_ramp(int vert)
5109 {
5110 	unsigned char *c0, *c1, *img, *dest, *buf, *mask;
5111 	int i, j, k, a, b, v, w, h, ww, gcor, opacity, bpp = MEM_BPP, rect[4];
5112 
5113 	if (marq_status != MARQUEE_DONE) return;
5114 
5115 	marquee_at(rect);
5116 	w = rect[2]; h = rect[3];
5117 	k = (vert ? h : w) - 1;
5118 	if (k < 2) return; // Nothing to do
5119 
5120 	ww = w * bpp;
5121 	buf = multialloc(MA_ALIGN_DEFAULT, &buf, ww, &mask, w, NULL);
5122 	if (!buf)
5123 	{
5124 		memory_errors(1);
5125 		return;
5126 	}
5127 
5128 	spot_undo(UNDO_FILT);
5129 
5130 	opacity = IS_INDEXED ? 0 : tool_opacity;
5131 	gcor = paint_gamma && (bpp == 3);
5132 	img = mem_img[mem_channel] + (rect[1] * mem_width + rect[0]) * bpp;
5133 	c0 = img;
5134 	c1 = img + (h - 1) * mem_width * bpp;
5135 	v = 0x3F; /* Vertical ramp: channel step 1, ramp step 0 */
5136 	vert = !!vert;
5137 	/* Horizontal ramp: for 1bpp channel step 0, ramp step 1;
5138 	 * for 3 bpp channel step (1, 1, -2), ramp step (0, 0, 1) */
5139 	if (!vert) ww -= bpp , v = bpp == 3 ? 0x0F : 0x2A;
5140 	/* Render the ramp row by row */
5141 	for (i = vert; i < h - vert; i++)
5142 	{
5143 		dest = img + i * mem_width * bpp;
5144 		if (vert) j = 0 , b = i;
5145 		else j = bpp , b = 1 , c0 = dest , c1 = dest + ww;
5146 		for (a = 0; j < ww; j++)
5147 		{
5148 			buf[j] = gcor ? UNGAMMA256((gamma256[c0[a]] * (k - b) +
5149 				gamma256[c1[a]] * b) / k) :
5150 				(c0[a] * (k - b) + c1[a] * b) / k;
5151 			a += (v & 3) - 2; // Step on channels
5152 			b += 1 - (v & 1); // Step on ramp
5153 			v = (v + ((v & 3) << 6)) >> 2; // Next step
5154 		}
5155 		row_protected(rect[0], rect[1] + i, w, mask);
5156 		if (!vert) mask[0] = mask[k] = 255; // Leave source pixels alone
5157 		process_mask(0, 1, w, mask, NULL, NULL, NULL, NULL, opacity, 0);
5158 		process_img(0, 1, w, mask, dest, dest, buf, buf, bpp, 0);
5159 	}
5160 
5161 	free(buf);
5162 	mem_undo_prepare();
5163 	update_stuff(UPD_IMG);
5164 }
5165 
5166 static int menu_view, menu_layer;
5167 static int menu_chan = ACTMOD(ACT_CHANNEL, CHN_IMAGE); // initial state
5168 
5169 static void *main_menu_code[] = {
5170 	REFv(main_menubar), SMARTMENU(menu_action),
5171 	SSUBMENU(_("/_File")),
5172 	MENUTEAR, //
5173 	MENUITEMis(_("//New"), ACTMOD(DLG_NEW, 0), XPM_ICON(new)),
5174 		SHORTCUT(n, C),
5175 	MENUITEMis(_("//Open ..."), ACTMOD(DLG_FSEL, FS_PNG_LOAD), XPM_ICON(open)),
5176 		SHORTCUT(o, C),
5177 	MENUITEMis(_("//Save"), ACTMOD(ACT_SAVE, 0), XPM_ICON(save)),
5178 		SHORTCUT(s, C),
5179 	MENUITEMs(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_PNG_SAVE)),
5180 	MENUSEP, //
5181 	MENUITEMs(_("//Export Undo Images ..."), ACTMOD(DLG_FSEL, FS_EXPORT_UNDO)),
5182 		ACTMAP(NEED_UNDO),
5183 	MENUITEMs(_("//Export Undo Images (reversed) ..."), ACTMOD(DLG_FSEL, FS_EXPORT_UNDO2)),
5184 		ACTMAP(NEED_UNDO),
5185 	MENUITEMs(_("//Export ASCII Art ..."), ACTMOD(DLG_FSEL, FS_EXPORT_ASCII)),
5186 		ACTMAP(NEED_IDX),
5187 	MENUITEMs(_("//Export Animated GIF ..."), ACTMOD(DLG_FSEL, FS_EXPORT_GIF)),
5188 		ACTMAP(NEED_IDX),
5189 	MENUSEP, //
5190 	SUBMENU(_("//Actions")),
5191 	MENUTEAR, ///
5192 	REFv(menu_slots[MENU_FACTION1]),
5193 	MENUITEM("///1", ACTMOD(ACT_FACTION, 1)),
5194 	REFv(menu_slots[MENU_FACTION2]),
5195 	MENUITEM("///2", ACTMOD(ACT_FACTION, 2)),
5196 	REFv(menu_slots[MENU_FACTION3]),
5197 	MENUITEM("///3", ACTMOD(ACT_FACTION, 3)),
5198 	REFv(menu_slots[MENU_FACTION4]),
5199 	MENUITEM("///4", ACTMOD(ACT_FACTION, 4)),
5200 	REFv(menu_slots[MENU_FACTION5]),
5201 	MENUITEM("///5", ACTMOD(ACT_FACTION, 5)),
5202 	REFv(menu_slots[MENU_FACTION6]),
5203 	MENUITEM("///6", ACTMOD(ACT_FACTION, 6)),
5204 	REFv(menu_slots[MENU_FACTION7]),
5205 	MENUITEM("///7", ACTMOD(ACT_FACTION, 7)),
5206 	REFv(menu_slots[MENU_FACTION8]),
5207 	MENUITEM("///8", ACTMOD(ACT_FACTION, 8)),
5208 	REFv(menu_slots[MENU_FACTION9]),
5209 	MENUITEM("///9", ACTMOD(ACT_FACTION, 9)),
5210 	REFv(menu_slots[MENU_FACTION10]),
5211 	MENUITEM("///10", ACTMOD(ACT_FACTION, 10)),
5212 	REFv(menu_slots[MENU_FACTION11]),
5213 	MENUITEM("///11", ACTMOD(ACT_FACTION, 11)),
5214 	REFv(menu_slots[MENU_FACTION12]),
5215 	MENUITEM("///12", ACTMOD(ACT_FACTION, 12)),
5216 	REFv(menu_slots[MENU_FACTION13]),
5217 	MENUITEM("///13", ACTMOD(ACT_FACTION, 13)),
5218 	REFv(menu_slots[MENU_FACTION14]),
5219 	MENUITEM("///14", ACTMOD(ACT_FACTION, 14)),
5220 	REFv(menu_slots[MENU_FACTION15]),
5221 	MENUITEM("///15", ACTMOD(ACT_FACTION, 15)),
5222 	REFv(menu_slots[MENU_FACTION_S]),
5223 	MENUSEPr, ///
5224 	MENUITEM(_("///Configure"), ACTMOD(DLG_FACTIONS, 0)),
5225 	WDONE,
5226 	REFv(menu_slots[MENU_RECENT_S]),
5227 	MENUSEPr, //
5228 	REFv(menu_slots[MENU_RECENT1]),
5229 	MENUITEM("//1", ACTMOD(ACT_LOAD_RECENT, 1)),
5230 		SHORTCUT(F1, CS),
5231 	REFv(menu_slots[MENU_RECENT2]),
5232 	MENUITEM("//2", ACTMOD(ACT_LOAD_RECENT, 2)),
5233 		SHORTCUT(F2, CS),
5234 	REFv(menu_slots[MENU_RECENT3]),
5235 	MENUITEM("//3", ACTMOD(ACT_LOAD_RECENT, 3)),
5236 		SHORTCUT(F3, CS),
5237 	REFv(menu_slots[MENU_RECENT4]),
5238 	MENUITEM("//4", ACTMOD(ACT_LOAD_RECENT, 4)),
5239 		SHORTCUT(F4, CS),
5240 	REFv(menu_slots[MENU_RECENT5]),
5241 	MENUITEM("//5", ACTMOD(ACT_LOAD_RECENT, 5)),
5242 		SHORTCUT(F5, CS),
5243 	REFv(menu_slots[MENU_RECENT6]),
5244 	MENUITEM("//6", ACTMOD(ACT_LOAD_RECENT, 6)),
5245 		SHORTCUT(F6, CS),
5246 	REFv(menu_slots[MENU_RECENT7]),
5247 	MENUITEM("//7", ACTMOD(ACT_LOAD_RECENT, 7)),
5248 		SHORTCUT(F7, CS),
5249 	REFv(menu_slots[MENU_RECENT8]),
5250 	MENUITEM("//8", ACTMOD(ACT_LOAD_RECENT, 8)),
5251 		SHORTCUT(F8, CS),
5252 	REFv(menu_slots[MENU_RECENT9]),
5253 	MENUITEM("//9", ACTMOD(ACT_LOAD_RECENT, 9)),
5254 		SHORTCUT(F9, CS),
5255 	REFv(menu_slots[MENU_RECENT10]),
5256 	MENUITEM("//10", ACTMOD(ACT_LOAD_RECENT, 10)),
5257 		SHORTCUT(F10, CS),
5258 	REFv(menu_slots[MENU_RECENT11]),
5259 	MENUITEM("//11", ACTMOD(ACT_LOAD_RECENT, 11)),
5260 	REFv(menu_slots[MENU_RECENT12]),
5261 	MENUITEM("//12", ACTMOD(ACT_LOAD_RECENT, 12)),
5262 	REFv(menu_slots[MENU_RECENT13]),
5263 	MENUITEM("//13", ACTMOD(ACT_LOAD_RECENT, 13)),
5264 	REFv(menu_slots[MENU_RECENT14]),
5265 	MENUITEM("//14", ACTMOD(ACT_LOAD_RECENT, 14)),
5266 	REFv(menu_slots[MENU_RECENT15]),
5267 	MENUITEM("//15", ACTMOD(ACT_LOAD_RECENT, 15)),
5268 	REFv(menu_slots[MENU_RECENT16]),
5269 	MENUITEM("//16", ACTMOD(ACT_LOAD_RECENT, 16)),
5270 	REFv(menu_slots[MENU_RECENT17]),
5271 	MENUITEM("//17", ACTMOD(ACT_LOAD_RECENT, 17)),
5272 	REFv(menu_slots[MENU_RECENT18]),
5273 	MENUITEM("//18", ACTMOD(ACT_LOAD_RECENT, 18)),
5274 	REFv(menu_slots[MENU_RECENT19]),
5275 	MENUITEM("//19", ACTMOD(ACT_LOAD_RECENT, 19)),
5276 	REFv(menu_slots[MENU_RECENT20]),
5277 	MENUITEM("//20", ACTMOD(ACT_LOAD_RECENT, 20)),
5278 	MENUSEP, //
5279 	MENUITEMs(_("//Quit"), ACTMOD(ACT_QUIT, 1)),
5280 		SHORTCUT(q, C),
5281 	WDONE,
5282 	SSUBMENU(_("/_Edit")),
5283 	MENUTEAR, //
5284 	MENUITEMis(_("//Undo"), ACTMOD(ACT_DO_UNDO, 0), XPM_ICON(undo)),
5285 		ACTMAP(NEED_UNDO), SHORTCUT(z, C),
5286 	MENUITEMis(_("//Redo"), ACTMOD(ACT_DO_UNDO, 1), XPM_ICON(redo)),
5287 		ACTMAP(NEED_REDO), SHORTCUT(r, C),
5288 	MENUSEP, //
5289 	MENUITEMis(_("//Cut"), ACTMOD(ACT_COPY, 1), XPM_ICON(cut)),
5290 		ACTMAP(NEED_SEL2), SHORTCUT(x, C),
5291 	MENUITEMis(_("//Copy"), ACTMOD(ACT_COPY, 0), XPM_ICON(copy)),
5292 		ACTMAP(NEED_SEL2), SHORTCUT(c, C),
5293 	MENUITEMs(_("//Copy To Palette"), ACTMOD(ACT_COPY_PAL, 0)),
5294 		ACTMAP(NEED_PSEL),
5295 	MENUITEMis(_("//Paste To Centre"), ACTMOD(ACT_PASTE, 1), XPM_ICON(paste)),
5296 		ACTMAP(NEED_CLIP), SHORTCUT(v, C),
5297 	MENUITEMs(_("//Paste To New Layer"), ACTMOD(ACT_LR_ADD, LR_PASTE)),
5298 		ACTMAP(NEED_PCLIP), SHORTCUT(v, CS),
5299 	MENUITEMs(_("//Paste"), ACTMOD(ACT_PASTE, 0)),
5300 		ACTMAP(NEED_CLIP), SHORTCUT(k, C),
5301 	MENUITEMis(_("//Paste Text"), ACTMOD(DLG_TEXT, 0), XPM_ICON(text)),
5302 		SHORTCUT(t, S),
5303 		IFvx(cmd_mode, 1), // Disable GUI-only text renderer
5304 			UNLESSv(texteng_con), ACTMAP(NEED_SKIP),
5305 		ENDIF(1),
5306 #ifdef U_FREETYPE
5307 	MENUITEMs(_("//Paste Text (FreeType)"), ACTMOD(DLG_TEXT_FT, 0)),
5308 		SHORTCUT(t, 0),
5309 #endif
5310 	MENUITEMs(_("//Paste Palette"), ACTMOD(ACT_PASTE_PAL, 0)),
5311 	MENUSEP, //
5312 	SUBMENU(_("//Load Clipboard")),
5313 	MENUTEAR, ///
5314 	MENUITEMs("///1", ACTMOD(ACT_LOAD_CLIP, 1)),
5315 		SHORTCUT(F1, S),
5316 	MENUITEMs("///2", ACTMOD(ACT_LOAD_CLIP, 2)),
5317 		SHORTCUT(F2, S),
5318 	MENUITEMs("///3", ACTMOD(ACT_LOAD_CLIP, 3)),
5319 		SHORTCUT(F3, S),
5320 	MENUITEMs("///4", ACTMOD(ACT_LOAD_CLIP, 4)),
5321 		SHORTCUT(F4, S),
5322 	MENUITEMs("///5", ACTMOD(ACT_LOAD_CLIP, 5)),
5323 		SHORTCUT(F5, S),
5324 	MENUITEMs("///6", ACTMOD(ACT_LOAD_CLIP, 6)),
5325 		SHORTCUT(F6, S),
5326 	MENUITEMs("///7", ACTMOD(ACT_LOAD_CLIP, 7)),
5327 		SHORTCUT(F7, S),
5328 	MENUITEMs("///8", ACTMOD(ACT_LOAD_CLIP, 8)),
5329 		SHORTCUT(F8, S),
5330 	MENUITEMs("///9", ACTMOD(ACT_LOAD_CLIP, 9)),
5331 		SHORTCUT(F9, S),
5332 	MENUITEMs("///10", ACTMOD(ACT_LOAD_CLIP, 10)),
5333 		SHORTCUT(F10, S),
5334 	MENUITEMs("///11", ACTMOD(ACT_LOAD_CLIP, 11)),
5335 		SHORTCUT(F11, S),
5336 	MENUITEMs("///12", ACTMOD(ACT_LOAD_CLIP, 12)),
5337 		SHORTCUT(F12, S),
5338 	WDONE,
5339 	SUBMENU(_("//Save Clipboard")),
5340 	MENUTEAR, ///
5341 	MENUITEMs("///1", ACTMOD(ACT_SAVE_CLIP, 1)),
5342 		ACTMAP(NEED_PCLIP), SHORTCUT(F1, C),
5343 	MENUITEMs("///2", ACTMOD(ACT_SAVE_CLIP, 2)),
5344 		ACTMAP(NEED_PCLIP), SHORTCUT(F2, C),
5345 	MENUITEMs("///3", ACTMOD(ACT_SAVE_CLIP, 3)),
5346 		ACTMAP(NEED_PCLIP), SHORTCUT(F3, C),
5347 	MENUITEMs("///4", ACTMOD(ACT_SAVE_CLIP, 4)),
5348 		ACTMAP(NEED_PCLIP), SHORTCUT(F4, C),
5349 	MENUITEMs("///5", ACTMOD(ACT_SAVE_CLIP, 5)),
5350 		ACTMAP(NEED_PCLIP), SHORTCUT(F5, C),
5351 	MENUITEMs("///6", ACTMOD(ACT_SAVE_CLIP, 6)),
5352 		ACTMAP(NEED_PCLIP), SHORTCUT(F6, C),
5353 	MENUITEMs("///7", ACTMOD(ACT_SAVE_CLIP, 7)),
5354 		ACTMAP(NEED_PCLIP), SHORTCUT(F7, C),
5355 	MENUITEMs("///8", ACTMOD(ACT_SAVE_CLIP, 8)),
5356 		ACTMAP(NEED_PCLIP), SHORTCUT(F8, C),
5357 	MENUITEMs("///9", ACTMOD(ACT_SAVE_CLIP, 9)),
5358 		ACTMAP(NEED_PCLIP), SHORTCUT(F9, C),
5359 	MENUITEMs("///10", ACTMOD(ACT_SAVE_CLIP, 10)),
5360 		ACTMAP(NEED_PCLIP), SHORTCUT(F10, C),
5361 	MENUITEMs("///11", ACTMOD(ACT_SAVE_CLIP, 11)),
5362 		ACTMAP(NEED_PCLIP), SHORTCUT(F11, C),
5363 	MENUITEMs("///12", ACTMOD(ACT_SAVE_CLIP, 12)),
5364 		ACTMAP(NEED_PCLIP), SHORTCUT(F12, C),
5365 	WDONE,
5366 	MENUITEM(_("//Import Clipboard from System"), ACTMOD(ACT_LOAD_CLIP, -1)),
5367 	MENUITEM(_("//Export Clipboard to System"), ACTMOD(ACT_SAVE_CLIP, -1)),
5368 		ACTMAP(NEED_PCLIP),
5369 	MENUSEP, //
5370 	MENUITEMs(_("//Choose Pattern ..."), ACTMOD(DLG_CHOOSER, CHOOSE_PATTERN)),
5371 		SHORTCUT(F2, 0),
5372 	MENUITEMs(_("//Choose Brush ..."), ACTMOD(DLG_CHOOSER, CHOOSE_BRUSH)),
5373 		SHORTCUT(F3, 0),
5374 	MENUITEMs(_("//Choose Colour ..."), ACTMOD(DLG_CHOOSER, CHOOSE_COLOR)),
5375 		SHORTCUT(e, 0),
5376 	// for scripting
5377 	uMENUITEMs("//layers", ACTMOD(ACT_SCRIPT, TOOLBAR_LAYERS)),
5378 	uMENUITEMs("//settings", ACTMOD(ACT_SCRIPT, TOOLBAR_SETTINGS)),
5379 	uMENUITEMs("//tools", ACTMOD(ACT_SCRIPT, TOOLBAR_TOOLS)),
5380 	WDONE,
5381 	SSUBMENU(_("/_View")),
5382 	MENUTEAR, //
5383 	MENUCHECKv(_("//Show Main Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_MAIN),
5384 		toolbar_status[TOOLBAR_MAIN]), SHORTCUT(F5, 0),
5385 	MENUCHECKv(_("//Show Tools Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_TOOLS),
5386 		toolbar_status[TOOLBAR_TOOLS]), SHORTCUT(F6, 0),
5387 	REFv(menu_slots[MENU_TBSET]),
5388 	MENUCHECKv(_("//Show Settings Toolbar"), ACTMOD(ACT_TBAR, TOOLBAR_SETTINGS),
5389 		toolbar_status[TOOLBAR_SETTINGS]), SHORTCUT(F7, 0),
5390 	REFv(menu_slots[MENU_DOCK]),
5391 	MENUCHECKv(_("//Show Dock"), ACTMOD(ACT_DOCK, 0), show_dock),
5392 		SHORTCUT(F12, 0), MTRIGGER(menu_action), // to show dock initially
5393 	MENUCHECKv(_("//Show Palette"), ACTMOD(ACT_TBAR, TOOLBAR_PALETTE),
5394 		toolbar_status[TOOLBAR_PALETTE]), SHORTCUT(F8, 0),
5395 	MENUCHECKv(_("//Show Status Bar"), ACTMOD(ACT_TBAR, TOOLBAR_STATUS),
5396 		toolbar_status[TOOLBAR_STATUS]),
5397 	MENUSEP, //
5398 	MENUITEM(_("//Toggle Image View"), ACTMOD(ACT_VIEW, 0)),
5399 		SHORTCUT(Home, 0), SHORTCUT(Home, C), SHORTCUT(Home, S),
5400 		SHORTCUT(Home, A), SHORTCUT(Home, CS), SHORTCUT(Home, CA),
5401 		SHORTCUT(Home, SA), SHORTCUT(Home, CSA),
5402 	MENUCHECKv(_("//Centralize Image"), ACTMOD(ACT_CENTER, 0), canvas_image_centre),
5403 	MENUCHECKv(_("//Show Zoom Grid"), ACTMOD(ACT_GRID, 0), mem_show_grid),
5404 	MENUCHECKvs(_("//Snap To Tile Grid"), ACTMOD(ACT_SNAP, 0), tgrid_snap),
5405 		SHORTCUT(b, 0),
5406 	MENUITEM(_("//Configure Grid ..."), ACTMOD(DLG_COLORS, COLSEL_GRID)),
5407 	MENUITEM(_("//Tracing Image ..."), ACTMOD(DLG_TRACE, 0)),
5408 	MENUSEP, //
5409 	REFv(menu_slots[MENU_VIEW]),
5410 	MENUCHECKv(_("//View Window"), ACTMOD(ACT_VWWIN, 0), menu_view),
5411 		SHORTCUT(v, 0),
5412 	MENUCHECKv(_("//Horizontal Split"), ACTMOD(ACT_VWSPLIT, 0), view_vsplit),
5413 		SHORTCUT(h, 0),
5414 	MENUCHECKv(_("//Focus View Window"), ACTMOD(ACT_VWFOCUS, 0), vw_focus_on),
5415 	MENUSEP, //
5416 	MENUITEMi(_("//Pan Window"), ACTMOD(ACT_PAN, 0), XPM_ICON(pan)),
5417 		SHORTCUT(End, 0),
5418 	REFv(menu_slots[MENU_LAYER]),
5419 	MENUCHECKv(_("//Layers Window"), ACTMOD(DLG_LAYERS, 0), menu_layer),
5420 		SHORTCUT(l, 0),
5421 	WDONE,
5422 	SSUBMENU(_("/_Image")),
5423 	MENUTEAR, //
5424 	uMENUITEMs("//Convert To RGB", ACTMOD(FILT_2RGB, 0)), // for scripting
5425 	MENUITEM(_("//Convert To RGB"), ACTMOD(FILT_2RGB, 0)),
5426 		ACTMAP(NEED_IDX),
5427 	MENUITEMs(_("//Convert To Indexed ..."), ACTMOD(DLG_INDEXED, 0)),
5428 		ACTMAP(NEED_24),
5429 	MENUSEP, //
5430 	MENUITEMs(_("//Scale Canvas ..."), ACTMOD(DLG_SCALE, 0)),
5431 		SHORTCUT(Page_Up, 0),
5432 	MENUITEMs(_("//Resize Canvas ..."), ACTMOD(DLG_SIZE, 0)),
5433 		SHORTCUT(Page_Down, 0),
5434 	MENUITEMs(_("//Crop"), ACTMOD(ACT_CROP, 0)),
5435 		ACTMAP(NEED_CROP), SHORTCUT(x, CS), SHORTCUT(Delete, 0),
5436 	MENUSEP, //
5437 	MENUITEMs(_("//Flip Vertically"), ACTMOD(ACT_FLIP_V, 0)),
5438 	MENUITEMs(_("//Flip Horizontally"), ACTMOD(ACT_FLIP_H, 0)),
5439 		SHORTCUT(m, C),
5440 	uMENUITEMs("//rotate", ACTMOD(DLG_ROTATE, 0)), // for scripting
5441 	MENUITEMs(_("//Rotate Clockwise"), ACTMOD(ACT_ROTATE, 0)),
5442 	MENUITEMs(_("//Rotate Anti-Clockwise"), ACTMOD(ACT_ROTATE, 1)),
5443 	MENUITEMs(_("//Free Rotate ..."), ACTMOD(DLG_ROTATE, 0)),
5444 	MENUITEMs(_("//Skew ..."), ACTMOD(DLG_SKEW, 0)),
5445 	MENUSEP, //
5446 // !!! Maybe support indexed mode too, later
5447 	MENUITEMs(_("//Segment ..."), ACTMOD(DLG_SEGMENT, 0)),
5448 		ACTMAP(NEED_24),
5449 	uMENUITEMs("//Script ...", ACTMOD(ACT_RUN_SCRIPT, 0)), // for scripting
5450 	REFv(menu_slots[MENU_SCRIPT]),
5451 	MENUITEM(_("//Script ..."), ACTMOD(DLG_SCRIPT, 0)),
5452 	REFv(menu_slots[MENU_SCRIPT_M]),
5453 	SUBMENU(_("//Scripts")),
5454 	MENUTEAR, ///
5455 	REFv(menu_slots[MENU_SCRIPT1]),
5456 	MENUITEMs("///1", ACTMOD(ACT_RUN_SCRIPT, 1)),
5457 	REFv(menu_slots[MENU_SCRIPT2]),
5458 	MENUITEMs("///2", ACTMOD(ACT_RUN_SCRIPT, 2)),
5459 	REFv(menu_slots[MENU_SCRIPT3]),
5460 	MENUITEMs("///3", ACTMOD(ACT_RUN_SCRIPT, 3)),
5461 	REFv(menu_slots[MENU_SCRIPT4]),
5462 	MENUITEMs("///4", ACTMOD(ACT_RUN_SCRIPT, 4)),
5463 	REFv(menu_slots[MENU_SCRIPT5]),
5464 	MENUITEMs("///5", ACTMOD(ACT_RUN_SCRIPT, 5)),
5465 	REFv(menu_slots[MENU_SCRIPT6]),
5466 	MENUITEMs("///6", ACTMOD(ACT_RUN_SCRIPT, 6)),
5467 	REFv(menu_slots[MENU_SCRIPT7]),
5468 	MENUITEMs("///7", ACTMOD(ACT_RUN_SCRIPT, 7)),
5469 	REFv(menu_slots[MENU_SCRIPT8]),
5470 	MENUITEMs("///8", ACTMOD(ACT_RUN_SCRIPT, 8)),
5471 	REFv(menu_slots[MENU_SCRIPT9]),
5472 	MENUITEMs("///9", ACTMOD(ACT_RUN_SCRIPT, 9)),
5473 	REFv(menu_slots[MENU_SCRIPT10]),
5474 	MENUITEMs("///10", ACTMOD(ACT_RUN_SCRIPT, 10)),
5475 	MENUSEP, ///
5476 	REFv(menu_slots[MENU_SCRIPTC]),
5477 	MENUITEM(_("///Configure"), ACTMOD(DLG_SCRIPT, 0)),
5478 	WDONE,
5479 	MENUSEP, //
5480 	MENUITEMs(_("//Information ..."), ACTMOD(DLG_INFO, 0)),
5481 		SHORTCUT(i, C),
5482 	REFv(menu_slots[MENU_PREFS]),
5483 	MENUITEMs(_("//Preferences ..."), ACTMOD(DLG_PREFS, 0)),
5484 		SHORTCUT(p, C),
5485 	WDONE,
5486 	SSUBMENU(_("/_Selection")),
5487 	MENUTEAR, //
5488 	MENUITEMs(_("//Select All"), ACTMOD(ACT_SELECT, 1)),
5489 		SHORTCUT(a, C),
5490 	MENUITEMs(_("//Select None (Esc)"), ACTMOD(ACT_SELECT, 0)),
5491 		ACTMAP(NEED_MARQ), SHORTCUT(a, CS),
5492 	MENUITEMis(_("//Lasso Selection"), ACTMOD(ACT_LASSO, 0), XPM_ICON(lasso)),
5493 		ACTMAP(NEED_LAS2), SHORTCUT(j, 0),
5494 	MENUITEMs(_("//Lasso Selection Cut"), ACTMOD(ACT_LASSO, 1)),
5495 		ACTMAP(NEED_LASSO),
5496 	MENUSEP, //
5497 	MENUITEMis(_("//Outline Selection"), ACTMOD(ACT_OUTLINE, 0), XPM_ICON(rect1)),
5498 		ACTMAP(NEED_SEL2), SHORTCUT(t, C),
5499 	MENUITEMis(_("//Fill Selection"), ACTMOD(ACT_OUTLINE, 1), XPM_ICON(rect2)),
5500 		ACTMAP(NEED_SEL2), SHORTCUT(t, CS),
5501 	MENUITEMis(_("//Outline Ellipse"), ACTMOD(ACT_ELLIPSE, 0), XPM_ICON(ellipse2)),
5502 		ACTMAP(NEED_SEL), SHORTCUT(l, C),
5503 	MENUITEMis(_("//Fill Ellipse"), ACTMOD(ACT_ELLIPSE, 1), XPM_ICON(ellipse)),
5504 		ACTMAP(NEED_SEL), SHORTCUT(l, CS),
5505 	MENUSEP, //
5506 	MENUITEMis(_("//Flip Vertically"), ACTMOD(ACT_SEL_FLIP_V, 0), XPM_ICON(flip_vs)),
5507 		ACTMAP(NEED_PCLIP),
5508 	MENUITEMis(_("//Flip Horizontally"), ACTMOD(ACT_SEL_FLIP_H, 0), XPM_ICON(flip_hs)),
5509 		ACTMAP(NEED_PCLIP),
5510 	MENUITEMis(_("//Rotate Clockwise"), ACTMOD(ACT_SEL_ROT, 0), XPM_ICON(rotate_cs)),
5511 		ACTMAP(NEED_PCLIP),
5512 	MENUITEMis(_("//Rotate Anti-Clockwise"), ACTMOD(ACT_SEL_ROT, 1), XPM_ICON(rotate_as)),
5513 		ACTMAP(NEED_PCLIP),
5514 	MENUSEP, //
5515 	MENUITEMs(_("//Horizontal Ramp"), ACTMOD(ACT_RAMP, 0)),
5516 		ACTMAP(NEED_SEL),
5517 	MENUITEMs(_("//Vertical Ramp"), ACTMOD(ACT_RAMP, 1)),
5518 		ACTMAP(NEED_SEL),
5519 	MENUSEP, //
5520 	MENUITEMs(_("//Alpha Blend A,B"), ACTMOD(ACT_SEL_ALPHA_AB, 0)),
5521 		ACTMAP(NEED_ACLIP),
5522 	MENUITEMs(_("//Move Alpha to Mask"), ACTMOD(ACT_SEL_ALPHAMASK, 0)),
5523 		ACTMAP(NEED_PCLIP),
5524 	MENUITEMs(_("//Mask Colour A,B"), ACTMOD(ACT_SEL_MASK_AB, 0)),
5525 		ACTMAP(NEED_CLIP),
5526 	MENUITEMs(_("//Unmask Colour A,B"), ACTMOD(ACT_SEL_MASK_AB, 255)),
5527 		ACTMAP(NEED_CLIP),
5528 	MENUITEMs(_("//Mask All Colours"), ACTMOD(ACT_SEL_MASK, 0)),
5529 		ACTMAP(NEED_PCLIP),
5530 	MENUITEMs(_("//Clear Mask"), ACTMOD(ACT_SEL_MASK, 255)),
5531 		ACTMAP(NEED_PCLIP),
5532 	WDONE,
5533 	SSUBMENU(_("/_Palette")),
5534 	uMENUITEMs("//a", ACTMOD(ACT_A, 0)), // for scripting
5535 	uMENUITEMs("//b", ACTMOD(ACT_B, 0)), // for scripting
5536 	MENUTEAR, //
5537 	MENUITEMis(_("//Load ..."), ACTMOD(DLG_FSEL, FS_PALETTE_LOAD), XPM_ICON(open)),
5538 	MENUITEMis(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_PALETTE_SAVE), XPM_ICON(save)),
5539 	MENUITEMs(_("//Load Default"), ACTMOD(ACT_PAL_DEF, 0)),
5540 	MENUSEP, //
5541 	MENUITEMs(_("//Mask All"), ACTMOD(ACT_PAL_MASK, 1)),
5542 	MENUITEMs(_("//Mask None"), ACTMOD(ACT_PAL_MASK, 0)),
5543 	MENUSEP, //
5544 	MENUITEMs(_("//Swap A & B"), ACTMOD(ACT_SWAP_AB, 0)),
5545 		SHORTCUT(x, 0),
5546 	MENUITEMs(_("//Edit Colour A & B ..."), ACTMOD(DLG_COLORS, COLSEL_EDIT_AB)),
5547 		SHORTCUT(e, C),
5548 	MENUITEMs(_("//Dither A"), ACTMOD(ACT_DITHER_A, 0)),
5549 		ACTMAP(NEED_24),
5550 	MENUITEMs(_("//Palette Editor ..."), ACTMOD(DLG_COLORS, COLSEL_EDIT_ALL)),
5551 		SHORTCUT(w, C),
5552 	MENUITEMs(_("//Set Palette Size ..."), ACTMOD(DLG_PAL_SIZE, 0)),
5553 	MENUITEMs(_("//Merge Duplicate Colours"), ACTMOD(ACT_PAL_MERGE, 0)),
5554 		ACTMAP(NEED_IDX),
5555 	MENUITEMs(_("//Remove Unused Colours"), ACTMOD(ACT_PAL_CLEAN, 0)),
5556 		ACTMAP(NEED_IDX),
5557 	MENUSEP, //
5558 	MENUITEMs(_("//Create Quantized ..."), ACTMOD(DLG_INDEXED, 1)),
5559 		ACTMAP(NEED_24),
5560 	MENUSEP, //
5561 	MENUITEMs(_("//Sort Colours ..."), ACTMOD(DLG_PAL_SORT, 0)),
5562 	MENUITEMs(_("//Palette Shifter ..."), ACTMOD(DLG_PAL_SHIFTER, 0)),
5563 	MENUITEMs(_("//Pick Gradient ..."), ACTMOD(DLG_PICK_GRAD, 0)),
5564 	WDONE,
5565 	SSUBMENU(_("/Effe_cts")),
5566 	MENUTEAR, //
5567 	MENUITEMis(_("//Transform Colour ..."), ACTMOD(DLG_BRCOSA, 0), XPM_ICON(brcosa)),
5568 		SHORTCUT(c, CS), SHORTCUT(Insert, 0),
5569 	MENUITEMs(_("//Invert"), ACTMOD(FILT_INVERT, 0)),
5570 		SHORTCUT(i, CS),
5571 	MENUITEMs(_("//Greyscale"), ACTMOD(FILT_GREY, 0)),
5572 		SHORTCUT(g, C),
5573 	MENUITEMs(_("//Greyscale (Gamma corrected)"), ACTMOD(FILT_GREY, 1)),
5574 		SHORTCUT(g, CS),
5575 	MENUITEMs(_("//Normalize"), ACTMOD(FILT_NORM, 0)),
5576 	MENUITEMs(_("//Threshold ..."), ACTMOD(DLG_XHOLD, 0)),
5577 	MENUITEMs(_("//Map ..."), ACTMOD(FILT_MAP, 0)),
5578 		ACTMAP(NEED_24),
5579 	SUBMENU(_("//Isometric Transformation")),
5580 	MENUTEAR, ///
5581 	MENUITEMs(_("///Left Side Down"), ACTMOD(ACT_ISOMETRY, 0)),
5582 	MENUITEMs(_("///Right Side Down"), ACTMOD(ACT_ISOMETRY, 1)),
5583 	MENUITEMs(_("///Top Side Right"), ACTMOD(ACT_ISOMETRY, 2)),
5584 	MENUITEMs(_("///Bottom Side Right"), ACTMOD(ACT_ISOMETRY, 3)),
5585 	WDONE,
5586 	MENUSEP, //
5587 	MENUITEMs(_("//Edge Detect ..."), ACTMOD(FILT_EDGE, 0)),
5588 		ACTMAP(NEED_NOIDX),
5589 	MENUITEMs(_("//Difference of Gaussians ..."), ACTMOD(FILT_DOG, 0)),
5590 		ACTMAP(NEED_NOIDX),
5591 	MENUITEMs(_("//Sharpen ..."), ACTMOD(FILT_SHARPEN, 0)),
5592 		ACTMAP(NEED_NOIDX),
5593 	MENUITEMs(_("//Unsharp Mask ..."), ACTMOD(FILT_UNSHARP, 0)),
5594 		ACTMAP(NEED_NOIDX),
5595 	MENUITEMs(_("//Soften ..."), ACTMOD(FILT_SOFTEN, 0)),
5596 		ACTMAP(NEED_NOIDX),
5597 	MENUITEMs(_("//Gaussian Blur ..."), ACTMOD(FILT_GAUSS, 0)),
5598 		ACTMAP(NEED_NOIDX),
5599 	MENUITEMs(_("//Kuwahara-Nagao Blur ..."), ACTMOD(FILT_KUWAHARA, 0)),
5600 		ACTMAP(NEED_24),
5601 	MENUITEMs(_("//Emboss"), ACTMOD(FILT_FX, FX_EMBOSS)),
5602 		ACTMAP(NEED_NOIDX),
5603 	MENUITEMs(_("//Dilate"), ACTMOD(FILT_FX, FX_DILATE)),
5604 		ACTMAP(NEED_NOIDX),
5605 	MENUITEMs(_("//Erode"), ACTMOD(FILT_FX, FX_ERODE)),
5606 		ACTMAP(NEED_NOIDX),
5607 	MENUSEP, //
5608 	MENUITEMs(_("//Solid Noise ..."), ACTMOD(DLG_NOISE, 0)),
5609 	MENUITEMs(_("//Bacteria ..."), ACTMOD(FILT_BACT, 0)),
5610 		ACTMAP(NEED_IDX),
5611 	WDONE,
5612 	SSUBMENU(_("/Cha_nnels")),
5613 	MENUTEAR, //
5614 	MENUITEMs(_("//New ..."), ACTMOD(ACT_CHANNEL, -1)),
5615 	MENUITEMis(_("//Load ..."), ACTMOD(DLG_FSEL, FS_CHANNEL_LOAD), XPM_ICON(open)),
5616 	MENUITEMis(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_CHANNEL_SAVE), XPM_ICON(save)),
5617 	MENUITEMs(_("//Delete ..."), ACTMOD(DLG_CHN_DEL, -1)),
5618 		ACTMAP(NEED_CHAN),
5619 	MENUSEP, //
5620 	REFv(menu_slots[MENU_CHAN0]),
5621 	MENURITEMvs(_("//Edit Image"), ACTMOD(ACT_CHANNEL, CHN_IMAGE), menu_chan),
5622 		SHORTCUT(1, S), SHORTCUT(KP_1, S),
5623 	REFv(menu_slots[MENU_CHAN1]),
5624 	MENURITEMvs(_("//Edit Alpha"), ACTMOD(ACT_CHANNEL, CHN_ALPHA), menu_chan),
5625 		SHORTCUT(2, S), SHORTCUT(KP_2, S),
5626 	REFv(menu_slots[MENU_CHAN2]),
5627 	MENURITEMvs(_("//Edit Selection"), ACTMOD(ACT_CHANNEL, CHN_SEL), menu_chan),
5628 		SHORTCUT(3, S), SHORTCUT(KP_3, S),
5629 	REFv(menu_slots[MENU_CHAN3]),
5630 	MENURITEMvs(_("//Edit Mask"), ACTMOD(ACT_CHANNEL, CHN_MASK), menu_chan),
5631 		SHORTCUT(4, S), SHORTCUT(KP_4, S),
5632 	MENUSEP, //
5633 	REFv(menu_slots[MENU_DCHAN0]),
5634 	MENUCHECKv(_("//Hide Image"), ACTMOD(ACT_SET_OVERLAY, 1), hide_image),
5635 		SHORTCUT(h, C),
5636 	REFv(menu_slots[MENU_DCHAN1]),
5637 	MENUCHECKvs(_("//Disable Alpha"), ACTMOD(ACT_CHN_DIS, CHN_ALPHA),
5638 		channel_dis[CHN_ALPHA]),
5639 	REFv(menu_slots[MENU_DCHAN2]),
5640 	MENUCHECKvs(_("//Disable Selection"), ACTMOD(ACT_CHN_DIS, CHN_SEL),
5641 		channel_dis[CHN_SEL]),
5642 	REFv(menu_slots[MENU_DCHAN3]),
5643 	MENUCHECKvs(_("//Disable Mask"), ACTMOD(ACT_CHN_DIS, CHN_MASK),
5644 		channel_dis[CHN_MASK]),
5645 	MENUSEP, //
5646 	MENUCHECKvs(_("//Couple RGBA Operations"), ACTMOD(ACT_SET_RGBA, 0), RGBA_mode),
5647 	MENUITEMs(_("//Threshold ..."), ACTMOD(FILT_THRES, 0)),
5648 	MENUITEMs(_("//Unassociate Alpha"), ACTMOD(FILT_UALPHA, 0)),
5649 		ACTMAP(NEED_RGBA),
5650 	MENUSEP, //
5651 	REFv(menu_slots[MENU_OALPHA]),
5652 	MENUCHECKv(_("//View Alpha as an Overlay"), ACTMOD(ACT_SET_OVERLAY, 0), overlay_alpha),
5653 	MENUITEM(_("//Configure Overlays ..."), ACTMOD(DLG_COLORS, COLSEL_OVERLAYS)),
5654 	WDONE,
5655 	SSUBMENU(_("/_Layers")),
5656 	MENUTEAR, //
5657 	MENUITEMis(_("//New Layer"), ACTMOD(ACT_LR_ADD, LR_NEW), XPM_ICON(new)),
5658 	MENUITEMis(_("//Save"), ACTMOD(ACT_LR_SAVE, 0), XPM_ICON(save)),
5659 		SHORTCUT(s, CS),
5660 	MENUITEMs(_("//Save As ..."), ACTMOD(DLG_FSEL, FS_LAYER_SAVE)),
5661 	MENUITEMs(_("//Save Composite Image ..."), ACTMOD(DLG_FSEL, FS_COMPOSITE_SAVE)),
5662 	MENUITEMs(_("//Composite to New Layer"), ACTMOD(ACT_LR_ADD, LR_COMP)),
5663 	MENUITEMs(_("//Remove All Layers"), ACTMOD(ACT_LR_DEL, 1)),
5664 	MENUSEP, //
5665 	MENUITEM(_("//Configure Animation ..."), ACTMOD(DLG_ANI, 0)),
5666 	MENUITEM(_("//Preview Animation ..."), ACTMOD(DLG_ANI_VIEW, 0)),
5667 	MENUITEM(_("//Set Key Frame ..."), ACTMOD(DLG_ANI_KEY, 0)),
5668 	MENUITEM(_("//Remove All Key Frames ..."), ACTMOD(DLG_ANI_KILLKEY, 0)),
5669 	WDONE,
5670 	SSUBMENU(_("/More...")),
5671 	WDONE,
5672 	ESUBMENU(_("/_Help")),
5673 	MENUITEM(_("//Documentation"), ACTMOD(ACT_DOCS, 0)),
5674 	REFv(menu_slots[MENU_HELP]),
5675 	MENUITEM(_("//About"), ACTMOD(DLG_ABOUT, 0)),
5676 		SHORTCUT(F1, 0),
5677 	MENUSEP, //
5678 	MENUITEM(_("//Keyboard Shortcuts ..."), ACTMOD(DLG_KEYS, 0)),
5679 	MENUITEM(_("//Rebind Shortcut Keycodes"), ACTMOD(ACT_REBIND_KEYS, 0)),
5680 	WDONE,
5681 	WDONE, // smartmenu
5682 	ENDSCRIPT,
5683 	RET
5684 };
5685 
dock_esc(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)5686 static int dock_esc(main_dd *dt, void **wdata, int what, void **where,
5687 	key_ext *keydata)
5688 {
5689 	/* Pressing Escape moves focus out of dock - to nowhere */
5690 	if (keydata->key == KEY(Escape))
5691 	{
5692 		cmd_setv(main_window_, NULL, WINDOW_FOCUS);
5693 		return (TRUE);
5694 	}
5695 	return (FALSE);
5696 }
5697 
cline_keypress(main_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)5698 static int cline_keypress(main_dd *dt, void **wdata, int what, void **where,
5699 	key_ext *keydata)
5700 {
5701 	return (check_zoom_keys(wtf_pressed(keydata))); // Check HOME/zoom keys
5702 }
5703 
cline_select(main_dd * dt,void ** wdata,int what,void ** where)5704 static void cline_select(main_dd *dt, void **wdata, int what, void **where)
5705 {
5706 	cmd_read(where, dt);
5707 
5708 	if (dt->idx_c == dt->nidx_c) return; // no change
5709 	if ((layers_total ? check_layers_for_changes() : check_for_changes()) == 1)
5710 		cmd_set(where, dt->idx_c); // Go back
5711 	// Load requested file
5712 	else do_a_load(file_args[dt->idx_c = dt->nidx_c], undo_load);
5713 }
5714 
dock_undock_evt(main_dd * dt,void ** wdata,int what,void ** where)5715 static void dock_undock_evt(main_dd *dt, void **wdata, int what, void **where)
5716 {
5717 	int dstate;
5718 
5719 	cmd_read(where, dt);
5720 	dstate = dt->settings_d | dt->layers_d;
5721 
5722 	/* Hide settings + layers page if it's empty */
5723 	cmd_showhide(dt->dockpage1, dstate);
5724 
5725 	/* Show tabs only when it makes sense */
5726 	cmd_setv(dock_book, (void *)(dt->cline_d && dstate), NBOOK_TABS);
5727 
5728 	/* Close dock if nothing left in it */
5729 	dstate |= dt->cline_d;
5730 	if (!dstate) cmd_set(menu_slots[MENU_DOCK], FALSE);
5731 	cmd_sensitive(menu_slots[MENU_DOCK], dstate);
5732 }
5733 
5734 #define REPAINT_CANVAS_COST 512
5735 
5736 #define WBbase main_dd
5737 static void *main_code[] = {
5738 ///	MAIN WINDOW
5739 	MAINWINDOW(MT_VERSION, icon_xpm, 100, 100), EVENT(CANCEL, delete_event),
5740 	EVENT(KEY, handle_keypress),
5741 	REF(drop), CLIPFORM(uri_list, 1),
5742 	/* !!! Konqueror needs GDK_ACTION_MOVE to do a drop; we never accept
5743 	 * move as a move, so have to do some non-default processing - WJ */
5744 	DRAGDROPm(drop, NULL, parse_drag),
5745 	WXYWH("window", 630, 400),
5746 ///	KEYMAP
5747 	REFv(main_keys), KEYMAP(keyslot, "main"),
5748 	CALL(keylist_code),
5749 	REFv(dock_area), DOCK("dockSize"),
5750 ///	MENU
5751 	CALL(main_menu_code),
5752 ///	TOOLBARS
5753 	CALL(toolbar_code),
5754 	XHBOX,
5755 ///	PALETTE
5756 	CALL(toolbar_palette_code),
5757 	XVBOX,
5758 ///	DRAWING AREA
5759 	REFv(main_split), HVSPLIT,
5760 //	MAIN WINDOW
5761 	REFv(scrolledwindow_canvas), CSCROLLv(cvxy), EVENT(CHANGE, vw_focus_idle),
5762 	REFv(drawing_canvas), CANVAS(48, 48, REPAINT_CANVAS_COST, paint_canvas),
5763 	EVENT(CHANGE, configure_canvas),
5764 	EVENT(XMOUSE, canvas_mouse), EVENT(RXMOUSE, canvas_mouse),
5765 	EVENT(MXMOUSE, canvas_mouse), EVENT(CROSS, canvas_enter_leave),
5766 	EVENT(SCROLL, canvas_scroll),
5767 //	VIEW WINDOW
5768 	CALL(init_view_code),
5769 	EVENT(SCROLL, canvas_scroll), // for vw_drawing widget
5770 	WDONE, // hvsplit
5771 ////	STATUS BAR
5772 	REFv(toolbar_boxes[TOOLBAR_STATUS]),
5773 	STATUSBAR, UNLESSv(toolbar_status[TOOLBAR_STATUS]), HIDDEN,
5774 	/* Labels visibility is set later by init_status_bar() */
5775 	REFv(label_bar[STATUS_GEOMETRY]), STLABEL(0, 0),
5776 	REFv(label_bar[STATUS_CURSORXY]), STLABEL(90, 1),
5777 	REFv(label_bar[STATUS_PIXELRGB]), STLABEL(0, 0),
5778 	REFv(label_bar[STATUS_SELEGEOM]), STLABELe(0, 0),
5779 	REFv(label_bar[STATUS_UNDOREDO]), STLABELe(70, 1),
5780 	WDONE, // statusbar
5781 	WDONE, // xvbox
5782 	WDONE, // xhbox
5783 	WDONE, // left pane
5784 	BORDER(NBOOK, 0),
5785 	REFv(dock_book), NBOOKr, KEEPWIDTH, WANTKEYS(dock_esc),
5786 	IFx(cline_d, 1),
5787 		PAGEi(XPM_ICON(cline), 0),
5788 		BORDER(SCROLL, 0),
5789 		XSCROLL(1, 1), // auto/auto
5790 		WLIST,
5791 		RTXTCOLUMNDi(0, 0),
5792 		COLUMNDATA(strs_c, sizeof(int)), CLEANUP(strs_c),
5793 		LISTCu(nidx_c, cnt_c, cline_select), FOCUS,
5794 		EVENT(KEY, cline_keypress),
5795 		WDONE,
5796 	ENDIF(1),
5797 	REF(dockpage1), PAGEir(XPM_ICON(layers), 5),
5798 	REFv(settings_dock),
5799 	MOUNT(settings_d, create_settings_box, dock_undock_evt),
5800 	HSEPt,
5801 	REFv(layers_dock),
5802 	PMOUNT(layers_d, create_layers_box, dock_undock_evt, "layers_h", 400),
5803 	TRIGGER,
5804 	WDONE, // page
5805 	WDONE, // nbook
5806 	WDONE, // right pane
5807 	REF(clip), CLIPFORM(clip_formats, CLIP_TARGETS),
5808 	REF(clipboard), CLIPBOARD(clip, 3, clipboard_export_fn, clipboard_import_fn),
5809 	RAISED, WEND
5810 };
5811 #undef WBbase
5812 
main_init()5813 void main_init()
5814 {
5815 	main_dd tdata;
5816 	char *tdev, txt[PATHTXT];
5817 
5818 	memset(&tdata, 0, sizeof(tdata));
5819 	/* Prepare commandline list */
5820 	if ((show_dock = tdata.cline_d = files_passed > 1))
5821 	{
5822 		memx2 mem;
5823 		int i, *p;
5824 
5825 		memset(&mem, 0, sizeof(mem));
5826 		getmemx2(&mem, 4000); // default size
5827 		mem.here += getmemx2(&mem, files_passed * sizeof(int)); // minsize
5828 		for (i = 0; i < files_passed; i++)
5829 		{
5830 			p = (int *)mem.buf + i;
5831 			*p = mem.here - ((char *)p - mem.buf);
5832 			/* Convert name string (to UTF-8 in GTK+2) and store it */
5833 			gtkuncpy(txt, file_args[i], sizeof(txt));
5834 			addstr(&mem, txt, 0);
5835 		}
5836 		tdata.cnt_c = files_passed;
5837 		tdata.strs_c = (int *)mem.buf;
5838 	}
5839 	show_dock |= inifile_get_gboolean("showDock", FALSE);
5840 	main_window_ = run_create_(main_code, &tdata, sizeof(tdata), script_cmds);
5841 
5842 	recent_files = bounded(recent_files, 0, MAX_RECENT);
5843 	update_recent_files(FALSE);
5844 
5845 	if (!tdata.cline_d) // Stops first icon in toolbar being selected
5846 		cmd_setv(main_window_, NULL, WINDOW_FOCUS);
5847 
5848 	init_status_bar();
5849 	init_factions();				// Initialize file action menu
5850 	update_script_menu();
5851 
5852 	file_in_homedir(txt, ".clipboard", PATHBUF);
5853 	strncpy0(mem_clip_file, inifile_get("clipFilename", txt), PATHBUF);
5854 
5855 	set_cursor(NULL);
5856 	change_to_tool(DEFAULT_TOOL_ICON);
5857 
5858 	/* Skip the GUI-specific updates in commandline mode */
5859 	if (cmd_mode) return;
5860 
5861 	cmd_peekv(main_window_, &tdev, sizeof(tdev), WDATA_TABLET); // Check tablet state
5862 	tablet_working = !!tdev;
5863 
5864 	cmd_showhide(main_window_, TRUE);
5865 
5866 	if (viewer_mode) toggle_view();
5867 	else toolbar_showhide();
5868 }
5869 
spot_undo(int mode)5870 void spot_undo(int mode)
5871 {
5872 	mem_undo_next(mode);		// Do memory stuff for undo
5873 	update_menus();			// Update menu undo issues
5874 }
5875 
5876 #ifdef U_NLS
5877 #include <locale.h>
setup_language()5878 void setup_language()			// Change language
5879 {
5880 	char *txt = inifile_get( "languageSETTING", "system" ), txt2[64];
5881 
5882 	if (strcmp("system", txt))
5883 	{
5884 		snprintf( txt2, 60, "LANGUAGE=%s", txt );
5885 		putenv( txt2 );
5886 		snprintf( txt2, 60, "LANG=%s", txt );
5887 		putenv( txt2 );
5888 		snprintf( txt2, 60, "LC_ALL=%s", txt );
5889 		putenv( txt2 );
5890 	}
5891 	else txt = "";
5892 
5893 
5894 #if GTK_MAJOR_VERSION > 1
5895 	if (cmd_mode)
5896 #endif
5897 	setlocale(LC_ALL, txt);
5898 #if GTK_MAJOR_VERSION <= 2
5899 	/* !!! Slow or not, but NLS is *really* broken on GTK+1 without it - WJ */
5900 	// GTK+1 hates this - it really slows things down
5901 	if (!cmd_mode) gtk_set_locale();
5902 #endif
5903 }
5904 #endif
5905 
update_titlebar()5906 void update_titlebar()		// Update filename in titlebar
5907 {
5908 	static int changed = -1;
5909 	static char *name = "";
5910 	char txt[300], txt2[PATHTXT];
5911 
5912 
5913 	/* Don't send needless updates */
5914 	if (!main_window_ || ((mem_changed == changed) && (mem_filename == name)))
5915 		return;
5916 	changed = mem_changed;
5917 	name = mem_filename;
5918 
5919 	snprintf(txt, 290, "%s %s %s", MT_VERSION,
5920 		changed ? __("(Modified)") : "-",
5921 		name ? gtkuncpy(txt2, name, PATHTXT) : __("Untitled"));
5922 
5923 	cmd_setv(main_window_, txt, WINDOW_TITLE);
5924 }
5925 
notify_changed()5926 void notify_changed()		// Image/palette has just changed - update vars as needed
5927 {
5928 	mem_tempfiles = NULL;
5929 	if (!mem_changed)
5930 	{
5931 		mem_changed = TRUE;
5932 		update_titlebar();
5933 	}
5934 }
5935 
5936 /* Image has just been unchanged: saved to file, or loaded if "filename" is NULL */
notify_unchanged(char * filename)5937 void notify_unchanged(char *filename)
5938 {
5939 	if (mem_changed)
5940 	{
5941 		if (filename) mem_file_modified(filename);
5942 		mem_changed = FALSE;
5943 		update_titlebar();
5944 	}
5945 }
5946 
5947