1 /*
2  ** A GTK widget showing a list of shapes from an image file.
3  **
4  ** Written: 7/25/99 - JSF
5  **/
6 
7 /*
8 Copyright (C) 1999-2020 Jeffrey S. Freedman
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 */
24 
25 #ifdef HAVE_CONFIG_H
26 #	include <config.h>
27 #endif
28 
29 #include "shapelst.h"
30 
31 #include "databuf.h"
32 #include "fontgen.h"
33 #include "frnameinf.h"
34 #include "ibuf8.h"
35 #include "items.h"
36 #include "pngio.h"
37 #include "shapefile.h"
38 #include "shapegroup.h"
39 #include "shapevga.h"
40 #include "studio.h"
41 #include "u7drag.h"
42 #include "utils.h"
43 
44 #include <glib.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
47 
48 #include <cmath>
49 #include <cstdlib>
50 #include <iostream>
51 #include <memory>
52 
53 #ifdef _WIN32
54 #include <windows.h>
55 #endif
56 
57 using EStudio::Add_menu_item;
58 using EStudio::Alert;
59 using EStudio::Prompt;
60 using std::cout;
61 using std::cerr;
62 using std::endl;
63 using std::ifstream;
64 using std::make_unique;
65 using std::string;
66 using std::strlen;
67 using std::unique_ptr;
68 
69 std::vector<std::unique_ptr<Editing_file>> Shape_chooser::editing_files;
70 int Shape_chooser::check_editing_timer = -1;
71 
72 #define IS_FLAT(shnum)  ((shnum) < c_first_obj_shape)
73 
74 /*
75  *  Here's a description of a file being edited by an external program
76  *  like Gimp or Photoshop.
77  */
78 class Editing_file {
79 	string vga_basename;        // Name of image file this comes from.
80 	string pathname;        // Full path to file.
81 	time_t mtime;           // Last modification time.
82 	int shapenum, framenum;     // Shape/frame.
83 	int tiles;          // If > 0, #8x8 tiles per row or col.
84 	bool bycolumns;         // If true tile by column first.
85 public:
86 	friend class Shape_chooser;
87 	// Create for single frame:
Editing_file(const char * vganm,const char * pnm,time_t m,int sh,int fr)88 	Editing_file(const char *vganm, const char *pnm, time_t m,
89 	             int sh, int fr)
90 		: vga_basename(vganm), pathname(pnm),
91 		  mtime(m), shapenum(sh), framenum(fr), tiles(0)
92 	{  }
93 	// Create tiled:
Editing_file(const char * vganm,const char * pnm,time_t m,int sh,int ts,bool bycol)94 	Editing_file(const char *vganm, const char *pnm, time_t m, int sh,
95 	             int ts, bool bycol)
96 		: vga_basename(vganm), pathname(pnm), mtime(m), shapenum(sh),
97 		  framenum(0), tiles(ts), bycolumns(bycol)
98 	{  }
99 };
100 
101 /*
102  *  Callback for when a shape is dropped on our draw area.
103  */
104 
Shape_dropped_here(int file,int shape,int frame,void * udata)105 static void Shape_dropped_here(
106     int file,           // U7_SHAPE_SHAPES.
107     int shape,
108     int frame,
109     void *udata
110 ) {
111 	static_cast<Shape_chooser *>(udata)->shape_dropped_here(file, shape, frame);
112 }
113 
114 /*
115  *  Blit onto screen.
116  */
117 
show(int x,int y,int w,int h)118 void Shape_chooser::show(
119     int x, int y, int w, int h  // Area to blit.
120 ) {
121 	Shape_draw::show(x, y, w, h);
122 	if ((selected >= 0) && (drawgc != nullptr)) {    // Show selected.
123 		TileRect b = info[selected].box;
124 		// Draw yellow box.
125 		cairo_set_line_width(drawgc, 1.0);
126 		cairo_set_source_rgb(drawgc,
127 		                     ((drawfg >> 16) & 255) / 255.0,
128 		                     ((drawfg >> 8) & 255) / 255.0,
129 		                     (drawfg & 255) / 255.0);
130 		cairo_rectangle(drawgc, b.x - hoffset, b.y - voffset, b.w, b.h);
131 		cairo_stroke(drawgc);
132 	}
133 }
134 
135 /*
136  *  Send selected shape/frame to Exult.
137  */
138 
tell_server_shape()139 void Shape_chooser::tell_server_shape(
140 ) {
141 	int shnum = -1;
142 	int frnum = 0;
143 	if (selected >= 0) {
144 		shnum = info[selected].shapenum;
145 		frnum = info[selected].framenum;
146 	}
147 	unsigned char buf[Exult_server::maxlength];
148 	unsigned char *ptr = &buf[0];
149 	Write2(ptr, shnum);
150 	Write2(ptr, frnum);
151 	ExultStudio *studio = ExultStudio::get_instance();
152 	studio->send_to_server(Exult_server::set_edit_shape, buf, ptr - buf);
153 }
154 
155 /*
156  *  Select an entry.  This should be called after rendering
157  *  the shape.
158  */
159 
select(int new_sel)160 void Shape_chooser::select(
161     int new_sel
162 ) {
163 	selected = new_sel;
164 	tell_server_shape();        // Tell Exult.
165 	int shapenum = info[selected].shapenum;
166 	// Update spin-button value, range.
167 	gtk_widget_set_sensitive(fspin, true);
168 	int nframes = ifile->get_num_frames(shapenum);
169 	gtk_adjustment_set_upper(frame_adj, nframes - 1);
170 	gtk_adjustment_set_value(frame_adj, info[selected].framenum);
171 	gtk_widget_set_sensitive(fspin, true);
172 	update_statusbar();
173 }
174 
175 const int border = 4;           // Border at bottom, sides.
176 /*
177  *  Render as many shapes as fit in the shape chooser window.
178  */
179 
render()180 void Shape_chooser::render(
181 ) {
182 	// Get drawing area dimensions.
183 	GtkAllocation alloc = {0, 0, 0, 0};
184 	gtk_widget_get_allocation(draw, &alloc);
185 	gint winh = alloc.height;
186 	// Clear window first.
187 	iwin->fill8(255);       // Set to background_color.
188 	int curr_y = -row0_voffset;
189 	//int total_cnt = get_count();
190 	//   filter (group).
191 	for (unsigned rownum = row0; curr_y  < winh && rownum < rows.size();
192 	        ++rownum) {
193 		Shape_row &row = rows[rownum];
194 		unsigned cols = get_num_cols(rownum);
195 		for (unsigned index = row.index0; cols; --cols, ++index) {
196 			int shapenum = info[index].shapenum;
197 			int framenum = info[index].framenum;
198 			Shape_frame *shape = ifile->get_shape(shapenum,
199 			                                      framenum);
200 			if (shape) {
201 				int sx = info[index].box.x - hoffset;
202 				int sy = info[index].box.y - voffset;
203 				shape->paint(iwin, sx + shape->get_xleft(),
204 				             sy + shape->get_yabove());
205 				last_shape = shapenum;
206 			}
207 		}
208 		curr_y += rows[rownum].height;
209 	}
210 	gtk_widget_queue_draw(draw);
211 }
212 
213 /*
214  *  Get maximum shape height for all its frames.
215  */
216 
Get_max_height(Shape * shape)217 static int Get_max_height(
218     Shape *shape
219 ) {
220 	int cnt = shape->get_num_frames();
221 	int maxh = 0;
222 	for (int i = 0; i < cnt; i++) {
223 		Shape_frame *frame = shape->get_frame(i);
224 		if (!frame) {
225 			continue;
226 		}
227 		int ht = frame->get_height();
228 		if (ht > maxh)
229 			maxh = ht;
230 	}
231 	return maxh;
232 }
233 
234 /*
235  *  Get the x-offset in pixels where a frame will be drawn.
236  *
237  *  Output: Offset from left edge of (virtual) drawing area.
238  */
239 
Get_x_offset(Shape * shape,int framenum)240 static int Get_x_offset(
241     Shape *shape,
242     int framenum
243 ) {
244 	if (!shape)
245 		return 0;
246 	int nframes = shape->get_num_frames();
247 	if (framenum >= nframes)
248 		framenum = nframes - 1;
249 	int xoff = 0;
250 	for (int i = 0; i < framenum; i++) {
251 		Shape_frame *fr = shape->get_frame(i);
252 		if (fr) {
253 			xoff += fr->get_width() + border;
254 		}
255 	}
256 	return xoff;
257 }
258 
259 /*
260  *  Find where everything goes.
261  */
262 
setup_info(bool savepos)263 void Shape_chooser::setup_info(
264     bool savepos            // Try to keep current position.
265 ) {
266 	unsigned oldind = rows[row0].index0;
267 	info.resize(0);
268 	rows.resize(0);
269 	row0 = row0_voffset = 0;
270 	last_shape = 0;
271 	/* +++++NOTE:  index0 is always 0 for the shape browse.  It should
272 	    probably be removed from the base Obj_browser class */
273 	index0 = 0;
274 	voffset = 0;
275 	total_height = 0;
276 	if (frames_mode)
277 		setup_frames_info();
278 	else
279 		setup_shapes_info();
280 	setup_vscrollbar();
281 	if (savepos)
282 		goto_index(oldind);
283 }
284 
285 /*
286  *  Setup info when not in 'frames' mode.
287  */
288 
setup_shapes_info()289 void Shape_chooser::setup_shapes_info(
290 ) {
291 	int selshape = -1;
292 	int selframe = -1;
293 	if (selected >= 0) {    // Save selection info.
294 		selshape = info[selected].shapenum;
295 		selframe = info[selected].framenum;
296 	}
297 	// Get drawing area dimensions.
298 	GtkAllocation alloc = {0, 0, 0, 0};
299 	gtk_widget_get_allocation(draw, &alloc);
300 	gint winw = alloc.width;
301 	int x = 0;
302 	int curr_y = 0;
303 	int row_h = 0;
304 	int total_cnt = get_count();
305 	//   filter (group).
306 	rows.resize(1);         // Start 1st row.
307 	rows[0].index0 = 0;
308 	rows[0].y = 0;
309 	for (int index = 0; index < total_cnt; index++) {
310 		int shapenum = group ? (*group)[index] : index;
311 		int framenum = shapenum == selshape ? selframe : framenum0;
312 		Shape_frame *shape = ifile->get_shape(shapenum, framenum);
313 		if (!shape)
314 			continue;
315 		int sh = shape->get_height();
316 		int sw = shape->get_width();
317 		// Check if we've exceeded max width
318 		if (x + sw > winw && x) {   // But don't leave row empty.
319 			// Next line.
320 			rows.back().height = row_h + border;
321 			curr_y += row_h + border;
322 			row_h = 0;
323 			x = 0;
324 			rows.emplace_back();
325 			rows.back().index0 = info.size();
326 			rows.back().y = curr_y;
327 		}
328 		if (sh > row_h)
329 			row_h = sh;
330 		int sy = curr_y + border; // Get top y-coord.
331 		// Store info. about where drawn.
332 		info.emplace_back();
333 		info.back().set(shapenum, framenum, x, sy, sw, sh);
334 		x += sw + border;
335 	}
336 	rows.back().height = row_h + border;
337 	total_height = curr_y + rows.back().height + border;
338 }
339 
340 /*
341  *  Setup one shape per row, showing its frames from left to right.
342  */
343 
setup_frames_info()344 void Shape_chooser::setup_frames_info(
345 ) {
346 	// Get drawing area dimensions.
347 	int curr_y = 0;
348 	int maxw = 0;
349 	unsigned total_cnt = get_count();
350 	//   filter (group).
351 	for (unsigned index = index0; index < total_cnt; index++) {
352 		int shapenum = group ? (*group)[index] : index;
353 		// Get all frames.
354 		Shape *shape = ifile->extract_shape(shapenum);
355 		int nframes = shape ? shape->get_num_frames() : 0;
356 		if (!nframes)
357 			continue;
358 		int row_h = Get_max_height(shape);
359 		rows.emplace_back();
360 		rows.back().index0 = info.size();
361 		rows.back().y = curr_y;
362 		rows.back().height = row_h + border;
363 		int x = 0;
364 		int sw;
365 		int sh;
366 		for (int framenum = 0; framenum < nframes; framenum++,
367 		        x += sw + border) {
368 			Shape_frame *frame = shape->get_frame(framenum);
369 			if (!frame) {
370 				sw = 0;
371 				continue;
372 			}
373 			sh = frame->get_height();
374 			sw = frame->get_width();
375 			int sy = curr_y + border; // Get top y-coord.
376 			// Store info. about where drawn.
377 			info.emplace_back();
378 			info.back().set(shapenum, framenum, x, sy, sw, sh);
379 		}
380 		if (x > maxw)
381 			maxw = x;
382 		// Next line.
383 		curr_y += row_h + border;
384 	}
385 	total_height = curr_y + border;
386 	setup_hscrollbar(maxw);
387 }
388 
389 /*
390  *  Horizontally scroll so that the selected frame is visible (in frames
391  *  mode).
392  */
393 
scroll_to_frame()394 void Shape_chooser::scroll_to_frame(
395 ) {
396 	if (selected >= 0) {    // Save selection info.
397 		int selshape = info[selected].shapenum;
398 		int selframe = info[selected].framenum;
399 		Shape *shape = ifile->extract_shape(selshape);
400 		int xoff = Get_x_offset(shape, selframe);
401 		if (xoff < hoffset) // Left of visual area?
402 			hoffset = xoff > border ? xoff - border : 0;
403 		else {
404 			GtkAllocation alloc = {0, 0, 0, 0};
405 			gtk_widget_get_allocation(draw, &alloc);
406 			gint winw = alloc.width;
407 			Shape_frame *fr = shape->get_frame(selframe);
408 			if (fr) {
409 				int sw = fr->get_width();
410 				if (xoff + sw + border - hoffset > winw)
411 					hoffset = xoff + sw + border - winw;
412 			}
413 		}
414 		GtkAdjustment *adj = gtk_range_get_adjustment(
415 		                         GTK_RANGE(hscroll));
416 		gtk_adjustment_set_value(adj, hoffset);
417 	}
418 }
419 
420 /*
421  *  Scroll so a desired index is in view.
422  */
423 
goto_index(unsigned index)424 void Shape_chooser::goto_index(
425     unsigned index           // Desired index in 'info'.
426 ) {
427 	if (index >= info.size())
428 		return;         // Illegal index or empty chooser.
429 	Shape_entry &inf = info[index]; // Already in view?
430 	unsigned midx = inf.box.x + inf.box.w / 2;
431 	unsigned midy = inf.box.y + inf.box.h / 2;
432 	TileRect winrect(hoffset, voffset, config_width, config_height);
433 	if (winrect.has_point(midx, midy))
434 		return;
435 	unsigned start = 0;
436 	unsigned count = rows.size();
437 	while (count > 1) {     // Binary search.
438 		unsigned mid = start + count / 2;
439 		if (index < rows[mid].index0)
440 			count = mid - start;
441 		else {
442 			count = (start + count) - mid;
443 			start = mid;
444 		}
445 	}
446 	if (start < rows.size()) {
447 		// Get to right spot again!
448 		GtkAdjustment *adj = gtk_range_get_adjustment(
449 		                         GTK_RANGE(vscroll));
450 		gtk_adjustment_set_value(adj, rows[start].y);
451 	}
452 }
453 
454 /*
455  *  Find an index (not necessarily the 1st) for a given shape #.
456  */
457 
find_shape(int shnum)458 int Shape_chooser::find_shape(
459     int shnum
460 ) {
461 	if (group) {        // They're not ordered.
462 		unsigned cnt = info.size();
463 		for (unsigned i = 0; i < cnt; ++i)
464 			if (info[i].shapenum == shnum)
465 				return i;
466 		return -1;
467 	}
468 	unsigned start = 0;
469 	unsigned count = info.size();
470 	while (count > 1) {     // Binary search.
471 		unsigned mid = start + count / 2;
472 		if (shnum < info[mid].shapenum)
473 			count = mid - start;
474 		else {
475 			count = (start + count) - mid;
476 			start = mid;
477 		}
478 	}
479 	if (start < info.size())
480 		return start;
481 	else
482 		return -1;
483 }
484 
485 /*
486  *  Configure the viewing window.
487  */
488 
Configure_chooser(GtkWidget * widget,GdkEventConfigure * event,gpointer data)489 static gint Configure_chooser(
490     GtkWidget *widget,      // The drawing area.
491     GdkEventConfigure *event,
492     gpointer data           // ->Shape_chooser
493 ) {
494 	ignore_unused_variable_warning(widget);
495 	auto *chooser = static_cast<Shape_chooser *>(data);
496 	return chooser->configure(event);
497 }
configure(GdkEventConfigure * event)498 gint Shape_chooser::configure(
499     GdkEventConfigure *event
500 ) {
501 	Shape_draw::configure();
502 	// Did the size change?
503 	if (event->width != config_width || event->height != config_height) {
504 		config_width = event->width;
505 		config_height = event->height;
506 		setup_info(true);
507 		render();
508 		update_statusbar();
509 	} else
510 		render();       // Same size?  Just render it.
511 	// Set handler for shape dropped here,
512 	//   BUT not more than once.
513 	if (drop_callback != Shape_dropped_here)
514 		enable_drop(Shape_dropped_here, this);
515 	return FALSE;
516 }
517 
518 /*
519  *  Handle an expose event.
520  */
521 
expose(GtkWidget * widget,cairo_t * cairo,gpointer data)522 gint Shape_chooser::expose(
523     GtkWidget *widget,      // The view window.
524     cairo_t *cairo,
525     gpointer data           // ->Shape_chooser.
526 ) {
527 	ignore_unused_variable_warning(widget);
528 	auto *chooser = static_cast<Shape_chooser *>(data);
529 	chooser->set_graphic_context(cairo);
530 	GdkRectangle area = { 0, 0, 0, 0 };
531 	gdk_cairo_get_clip_rectangle(cairo, &area);
532 	chooser->show(area.x, area.y, area.width, area.height);
533 	chooser->set_graphic_context(nullptr);
534 	return TRUE;
535 }
536 
537 /*
538  *  Handle a mouse drag event.
539  */
540 
drag_motion(GtkWidget * widget,GdkEventMotion * event,gpointer data)541 gint Shape_chooser::drag_motion(
542     GtkWidget *widget,      // The view window.
543     GdkEventMotion *event,
544     gpointer data           // ->Shape_chooser.
545 ) {
546 	ignore_unused_variable_warning(widget);
547 	auto *chooser = static_cast<Shape_chooser *>(data);
548 	if (!chooser->dragging && chooser->selected >= 0)
549 		chooser->start_drag(U7_TARGET_SHAPEID_NAME,
550 		                    U7_TARGET_SHAPEID, reinterpret_cast<GdkEvent *>(event));
551 	return true;
552 }
553 
554 /*
555  *  Handle a mouse button-press event.
556  */
mouse_press(GtkWidget * widget,GdkEventButton * event)557 gint Shape_chooser::mouse_press(
558     GtkWidget *widget,      // The view window.
559     GdkEventButton *event
560 ) {
561 	gtk_widget_grab_focus(widget);
562 
563 	if (event->button == 4) {
564 		if (row0 > 0)
565 			scroll_row_vertical(row0 - 1);
566 		return TRUE;
567 	} else if (event->button == 5) {
568 		scroll_row_vertical(row0 + 1);
569 		return TRUE;
570 	}
571 	int old_selected = selected;
572 	int new_selected = -1;
573 	unsigned i;              // Search through entries.
574 	unsigned infosz = info.size();
575 	int absx = static_cast<int>(event->x) + hoffset;
576 	int absy = static_cast<int>(event->y) + voffset;
577 	for (i = rows[row0].index0; i < infosz; i++) {
578 		if (info[i].box.distance(absx, absy) <= 2) {
579 			// Found the box?
580 			// Indicate we can drag.
581 			new_selected = i;
582 			break;
583 		} else if (info[i].box.y - voffset >= config_height)
584 			break;      // Past bottom of screen.
585 	}
586 	if (new_selected >= 0) {
587 		select(new_selected);
588 		render();
589 		if (sel_changed)    // Tell client.
590 			(*sel_changed)();
591 	}
592 	if (new_selected < 0 && event->button == 1)
593 		unselect(true);     // No selection.
594 	else if (selected == old_selected && old_selected >= 0) {
595 		// Same square.  Check for dbl-click.
596 		if (reinterpret_cast<GdkEvent *>(event)->type == GDK_2BUTTON_PRESS)
597 			edit_shape_info();
598 	}
599 	if (event->button == 3)
600 		gtk_menu_popup_at_pointer(GTK_MENU(create_popup()),
601 		                          reinterpret_cast<GdkEvent *>(event));
602 	return TRUE;
603 }
604 
605 /*
606  *  Handle mouse button press/release events.
607  */
Mouse_press(GtkWidget * widget,GdkEventButton * event,gpointer data)608 static gint Mouse_press(
609     GtkWidget *widget,      // The view window.
610     GdkEventButton *event,
611     gpointer data           // ->Shape_chooser.
612 ) {
613 	auto *chooser = static_cast<Shape_chooser *>(data);
614 	return chooser->mouse_press(widget, event);
615 }
Mouse_release(GtkWidget * widget,GdkEventButton * event,gpointer data)616 static gint Mouse_release(
617     GtkWidget *widget,      // The view window.
618     GdkEventButton *event,
619     gpointer data           // ->Shape_chooser.
620 ) {
621 	ignore_unused_variable_warning(widget, event);
622 	auto *chooser = static_cast<Shape_chooser *>(data);
623 	chooser->mouse_up();
624 	return true;
625 }
626 
627 /*
628  *  Keystroke in draw-area.
629  */
630 C_EXPORT gboolean
on_draw_key_press(GtkEntry * entry,GdkEventKey * event,gpointer user_data)631 on_draw_key_press(GtkEntry   *entry,
632                   GdkEventKey    *event,
633                   gpointer    user_data) {
634 	ignore_unused_variable_warning(entry);
635 	auto *chooser = static_cast<Shape_chooser *>(user_data);
636 	switch (event->keyval) {
637 	case GDK_KEY_Delete:
638 		chooser->del_frame();
639 		return TRUE;
640 	case GDK_KEY_Insert:
641 		chooser->new_frame();
642 		return TRUE;
643 	}
644 	return FALSE;           // Let parent handle it.
645 }
646 
647 const unsigned char transp = 255;
648 
649 /*
650  *  Export the currently selected frame as a .png file.
651  *
652  *  Output: Modification time of file written, or 0 if error.
653  */
654 
export_png(const char * fname)655 time_t Shape_chooser::export_png(
656     const char *fname       // File to write out.
657 ) {
658 	int shnum = info[selected].shapenum;
659 	int frnum = info[selected].framenum;
660 	Shape_frame *frame = ifile->get_shape(shnum, frnum);
661 	int w = frame->get_width();
662 	int h = frame->get_height();
663 	Image_buffer8 img(w, h);    // Render into a buffer.
664 	img.fill8(transp);      // Fill with transparent pixel.
665 	frame->paint(&img, frame->get_xleft(), frame->get_yabove());
666 	int xoff = 0;
667 	int yoff = 0;
668 	if (frame->is_rle()) {
669 		xoff = -frame->get_xright();
670 		yoff = -frame->get_ybelow();
671 	}
672 	return export_png(fname, img, xoff, yoff);
673 }
674 
675 /*
676  *  Convert a GDK color map to a 3*256 byte RGB palette.
677  */
678 
Get_rgb_palette(ExultRgbCmap * palette,unsigned char * buf)679 static void Get_rgb_palette(
680     ExultRgbCmap *palette,
681     unsigned char *buf      // 768 bytes (3*256).
682 ) {
683 	for (int i = 0; i < 256; i++) {
684 		buf[3 * i] = (palette->colors[i] >> 16) & 0xff;
685 		buf[3 * i + 1] = (palette->colors[i] >> 8) & 0xff;
686 		buf[3 * i + 2] = palette->colors[i] & 0xff;
687 	}
688 }
689 
690 /*
691  *  Export an image as a .png file.
692  *
693  *  Output: Modification time of file written, or 0 if error.
694  */
695 
export_png(const char * fname,Image_buffer8 & img,int xoff,int yoff)696 time_t Shape_chooser::export_png(
697     const char *fname,      // File to write out.
698     Image_buffer8 &img,     // Image.
699     int xoff, int yoff      // Offset (from bottom-right).
700 ) {
701 	unsigned char pal[3 * 256]; // Set up palette.
702 	Get_rgb_palette(palette, pal);
703 	int w = img.get_width();
704 	int h = img.get_height();
705 	struct stat fs;         // Write out to the .png.
706 	// (Rotate transp. pixel to 0 for the
707 	//   Gimp's sake.)
708 	if (!Export_png8(fname, transp, w, h, w, xoff, yoff, img.get_bits(),
709 	                 &pal[0], 256, true) ||
710 	        stat(fname, &fs) != 0) {
711 		Alert("Error creating '%s'", fname);
712 		return 0;
713 	}
714 	return fs.st_mtime;
715 }
716 
717 /*
718  *  Export the current shape (which better be 8x8 flat) as a tiled PNG.
719  *
720  *  Output: modification time of file, or 0 if error.
721  */
722 
export_tiled_png(const char * fname,int tiles,bool bycols)723 time_t Shape_chooser::export_tiled_png(
724     const char *fname,      // File to write out.
725     int tiles,          // If #0, write all frames as tiles,
726     //   this many in each row (col).
727     bool bycols         // Write tiles columns-first.
728 ) {
729 	assert(selected >= 0);
730 	int shnum = info[selected].shapenum;
731 	ExultStudio *studio = ExultStudio::get_instance();
732 	// Low shape in 'shapes.vga'?
733 	assert(IS_FLAT(shnum) && file_info == studio->get_vgafile());
734 	Shape *shape = ifile->extract_shape(shnum);
735 	assert(shape != nullptr);
736 	cout << "Writing " << fname << " tiled"
737 	     << (bycols ? ", by cols" : ", by rows") << " first" << endl;
738 	int nframes = shape->get_num_frames();
739 	// Figure #tiles in other dim.
740 	int dim1_cnt = (nframes + tiles - 1) / tiles;
741 	int w;
742 	int h;
743 	if (bycols) {
744 		h = tiles * c_tilesize;
745 		w = dim1_cnt * c_tilesize;
746 	} else {
747 		w = tiles * c_tilesize;
748 		h = dim1_cnt * c_tilesize;
749 	}
750 	Image_buffer8 img(w, h);
751 	img.fill8(transp);      // Fill with transparent pixel.
752 	for (int f = 0; f < nframes; f++) {
753 		Shape_frame *frame = shape->get_frame(f);
754 		if (!frame)
755 			continue;   // We'll just leave empty ones blank.
756 		if (frame->is_rle() || frame->get_width() != c_tilesize ||
757 		        frame->get_height() != c_tilesize) {
758 			char buf[250];
759 			snprintf(buf, sizeof(buf), "Can only tile %dx%d flat shapes",
760 			         c_tilesize, c_tilesize);
761 			Alert("%s", buf);
762 			return 0;
763 		}
764 		int x;
765 		int y;
766 		if (bycols) {
767 			y = f % tiles;
768 			x = f / tiles;
769 		} else {
770 			x = f % tiles;
771 			y = f / tiles;
772 		}
773 		frame->paint(&img, x * c_tilesize + frame->get_xleft(),
774 		             y * c_tilesize + frame->get_yabove());
775 	}
776 	// Write out to the .png.
777 	return export_png(fname, img, 0, 0);
778 }
779 
780 /*
781  *  Bring up the shape-info editor for the selected shape.
782  */
783 
edit_shape_info()784 void Shape_chooser::edit_shape_info(
785 ) {
786 	ExultStudio *studio = ExultStudio::get_instance();
787 	int shnum = info[selected].shapenum;
788 	int frnum = info[selected].framenum;
789 	Shape_info *info = nullptr;
790 	const char *name = nullptr;
791 	if (shapes_file) {
792 		// Read info. the first time.
793 		if (shapes_file->read_info(studio->get_game_type(), true))
794 			studio->set_shapeinfo_modified();
795 		if (!shapes_file->has_info(shnum))
796 			shapes_file->set_info(shnum, Shape_info());
797 		info = &shapes_file->get_info(shnum);
798 		name = studio->get_shape_name(shnum);
799 	}
800 	studio->open_shape_window(shnum, frnum, file_info, name, info);
801 }
802 
803 /*
804  *  Bring up the user's image-editor for the selected shape.
805  */
806 
edit_shape(int tiles,bool bycols)807 void Shape_chooser::edit_shape(
808     int tiles,          // If #0, write all frames as tiles,
809     //   this many in each row (col).
810     bool bycols         // Write tiles columns-first.
811 ) {
812 	ExultStudio *studio = ExultStudio::get_instance();
813 	int shnum = info[selected].shapenum;
814 	int frnum = info[selected].framenum;
815 	string filestr("<SAVEGAME>");   // Set up filename.
816 	filestr += "/itmp";     // "Image tmp" directory.
817 	U7mkdir(filestr.c_str(), 0755); // Create if not already there.
818 	// Lookup <SAVEGAME>.
819 	filestr = get_system_path(filestr);
820 	char *ext;
821 	if (!tiles)         // Create name from file,shape,frame.
822 		ext = g_strdup_printf("/%s.s%d_f%d.png",
823 		                      file_info->get_basename(), shnum, frnum);
824 	else                // Tiled.
825 		ext = g_strdup_printf("/%s.s%d_%c%d.png",
826 		                      file_info->get_basename(), shnum,
827 		                      (bycols ? 'c' : 'r'), tiles);
828 	filestr += ext;
829 	g_free(ext);
830 	const char *fname = filestr.c_str();
831 	cout << "Writing image '" << fname << "'" << endl;
832 	time_t mtime;
833 	if (!tiles) {       // One frame?
834 		mtime = export_png(fname);
835 		if (!mtime)
836 			return;
837 		// Store info. about file.
838 		editing_files.push_back(std::make_unique<Editing_file>(
839 		                            file_info->get_basename(), fname, mtime, shnum, frnum));
840 	} else {
841 		mtime = export_tiled_png(fname, tiles, bycols);
842 		if (!mtime)
843 			return;
844 		editing_files.push_back(std::make_unique<Editing_file>(
845 		                            file_info->get_basename(), fname, mtime, shnum,
846 		                            tiles, bycols));
847 	}
848 	string cmd(studio->get_image_editor());
849 	cmd += ' ';
850 	string imgpath;
851 #ifdef _WIN32
852 	if (fname[0] == '.' && (fname[1] == '\\' || fname[1] == '/')) {
853 		char currdir[MAX_PATH];
854 		GetCurrentDirectory(MAX_PATH, currdir);
855 		imgpath += currdir;
856 		if (imgpath[imgpath.length() - 1] != '\\')
857 			imgpath += '\\';
858 	}
859 #endif
860 	imgpath += fname;
861 	// handle spaces in path.
862 	if (imgpath.find(' ') != string::npos)
863 		imgpath = "\"" + imgpath + "\"";
864 	cmd += imgpath;
865 #ifndef _WIN32
866 	cmd += " &";            // Background.
867 	int ret = system(cmd.c_str());
868 	if (ret == 127 || ret == -1)
869 		Alert("Can't launch '%s'", studio->get_image_editor());
870 #else
871 	for (char &ch : cmd)
872 		if (ch == '/')
873 			ch =  '\\';
874 	PROCESS_INFORMATION pi;
875 	STARTUPINFO     si;
876 	std::memset(&si, 0, sizeof(si));
877 	si.cb = sizeof(si);
878 	int ret = CreateProcess(nullptr, &cmd[0],
879 	                        nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
880 	if (!ret)
881 		Alert("Can't launch '%s'", studio->get_image_editor());
882 #endif
883 	if (check_editing_timer == -1)  // Monitor files every 6 seconds.
884 		check_editing_timer = g_timeout_add(6000,
885 		                                    Shape_chooser::check_editing_files_cb, nullptr);
886 }
887 
888 /*
889  *  Check the list of files being edited externally, and read in any that
890  *  have changed.
891  *
892  *  Output: 1 always.
893  */
894 
check_editing_files_cb(gpointer data)895 gint Shape_chooser::check_editing_files_cb(
896     gpointer data
897 ) {
898 	ignore_unused_variable_warning(data);
899 	ExultStudio *studio = ExultStudio::get_instance();
900 	// Is focus in main window?
901 	if (studio->has_focus())
902 		check_editing_files();
903 	return 1;           // Keep repeating.
904 }
905 // This one doesn't check focus.
check_editing_files()906 gint Shape_chooser::check_editing_files(
907 ) {
908 	bool modified = false;
909 	for (auto &ed : editing_files) {
910 		struct stat fs;     // Check mod. time of file.
911 		if (stat(ed->pathname.c_str(), &fs) != 0) {
912 			// Gone?
913 			ed.reset();
914 			continue;
915 		}
916 		if (fs.st_mtime <= ed->mtime)
917 			continue;   // Still not changed.
918 		ed->mtime = fs.st_mtime;
919 		read_back_edited(ed.get());
920 		modified = true;    // Did one.
921 	}
922 	editing_files.erase(std::remove_if(editing_files.begin(), editing_files.end(),
923 	[](const auto & ed) {
924 		return ed == nullptr;
925 	}), editing_files.end());
926 	if (modified) {         // Repaint if modified.
927 		ExultStudio *studio = ExultStudio::get_instance();
928 		Object_browser *browser = studio->get_browser();
929 		if (browser) {
930 			// Repaint main window.
931 			browser->render();
932 		}
933 		studio->update_group_windows(nullptr);
934 	}
935 	return 1;           // Continue timeouts.
936 }
937 
938 /*
939  *  Find the closest color in a palette to a given one.
940  *
941  *  Output: 0-254, whichever has closest color.
942  */
943 
Find_closest_color(const unsigned char * pal,int r,int g,int b)944 static int Find_closest_color(
945     const unsigned char *pal,     // 3*255 bytes.
946     int r, int g, int b     // Color to match.
947 ) {
948 	int best_index = -1;
949 	long best_distance = 0xfffffff;
950 	// Be sure to search rotating colors too.
951 	for (int i = 0; i < 0xff; i++) {
952 		// Get deltas.
953 		long dr = r - pal[3 * i];
954 		long dg = g - pal[3 * i + 1];
955 		long db = b - pal[3 * i + 2];
956 		// Figure distance-squared.
957 		long dist = dr * dr + dg * dg + db * db;
958 		if (dist < best_distance) {
959 			// Better than prev?
960 			best_index = i;
961 			best_distance = dist;
962 		}
963 	}
964 	return best_index;
965 }
966 
967 /*
968  *  Convert an 8-bit image in one palette to another.
969  */
970 
Convert_indexed_image(unsigned char * pixels,int count,unsigned char * oldpal,int oldpalsize,unsigned char * newpal)971 static void Convert_indexed_image(
972     unsigned char *pixels,      // Pixels.
973     int count,          // # pixels.
974     unsigned char *oldpal,      // Palette pixels currently uses.
975     int oldpalsize,         // Size of old palette.
976     unsigned char *newpal       // Palette (255 bytes) to convert to.
977 ) {
978 	if (memcmp(oldpal, newpal, oldpalsize) == 0)
979 		return;         // Old palette matches new.
980 	int map[256];           // Set up old->new map.
981 	int i;
982 	for (i = 0; i < 256; i++)   // Set to 'unknown'.
983 		map[i] = -1;
984 	map[transp] = transp;       // But leave transparent pix. alone.
985 	// Go through pixels.
986 	for (i = 0; i < count; i++) {
987 		unsigned char pix = *pixels;
988 		if (map[pix] == -1) // New one?
989 			map[pix] = Find_closest_color(newpal, oldpal[3 * pix],
990 			                              oldpal[3 * pix + 1], oldpal[3 * pix + 2]);
991 		*pixels++ = map[pix];
992 	}
993 }
994 
995 /*
996  *  Import a PNG file into a given shape,frame.
997  */
998 
Import_png(const char * fname,Shape_file_info * finfo,unsigned char * pal,int shapenum,int framenum)999 static void Import_png(
1000     const char *fname,      // Filename.
1001     Shape_file_info *finfo,     // What we're updating.
1002     unsigned char *pal,     // 3*255 bytes game palette.
1003     int shapenum, int framenum  // Shape, frame to update
1004 ) {
1005 	ExultStudio *studio = ExultStudio::get_instance();
1006 	Vga_file *ifile = finfo->get_ifile();
1007 	if (!ifile)
1008 		return;         // Shouldn't happen.
1009 	Shape *shape = ifile->extract_shape(shapenum);
1010 	if (!shape)
1011 		return;
1012 	int w;
1013 	int h;
1014 	int rowsize;
1015 	int xoff;
1016 	int yoff;
1017 	int palsize;
1018 	unsigned char *pixels;
1019 	unsigned char *oldpal;
1020 	// Import, with 255 = transp. index.
1021 	if (!Import_png8(fname, 255, w, h, rowsize, xoff, yoff,
1022 	                 pixels, oldpal, palsize))
1023 		return;         // Just return if error, for now.
1024 	// Convert to game palette.
1025 	Convert_indexed_image(pixels, h * rowsize, oldpal, palsize, pal);
1026 	delete [] oldpal;
1027 	// Low shape in 'shapes.vga'?
1028 	bool flat = IS_FLAT(shapenum) && finfo == studio->get_vgafile();
1029 	int xleft;
1030 	int yabove;
1031 	if (flat) {
1032 		xleft = yabove = c_tilesize;
1033 		if (w != c_tilesize || h != c_tilesize || rowsize != c_tilesize) {
1034 			char *msg = g_strdup_printf(
1035 			                "Shape %d must be %dx%d", shapenum, c_tilesize, c_tilesize);
1036 			studio->prompt(msg, "Continue");
1037 			g_free(msg);
1038 			delete [] pixels;
1039 			return;
1040 		}
1041 	} else {            // RLE. xoff,yoff are neg. from bottom.
1042 		xleft = w + xoff - 1;
1043 		yabove = h + yoff - 1;
1044 	}
1045 	shape->set_frame(make_unique<Shape_frame>(pixels,
1046 	                 w, h, xleft, yabove, !flat), framenum);
1047 	delete [] pixels;
1048 	finfo->set_modified();
1049 }
1050 
1051 /*
1052  *  Import a tiled PNG file into a given shape's frames.
1053  */
1054 
Import_png_tiles(const char * fname,Shape_file_info * finfo,unsigned char * pal,int shapenum,int tiles,bool bycols)1055 static void Import_png_tiles(
1056     const char *fname,      // Filename.
1057     Shape_file_info *finfo,     // What we're updating.
1058     unsigned char *pal,     // 3*255 bytes game palette.
1059     int shapenum,
1060     int tiles,          // #tiles per row/col.
1061     bool bycols         // Write tiles columns-first.
1062 ) {
1063 	Vga_file *ifile = finfo->get_ifile();
1064 	if (!ifile)
1065 		return;         // Shouldn't happen.
1066 	Shape *shape = ifile->extract_shape(shapenum);
1067 	if (!shape)
1068 		return;
1069 	int nframes = shape->get_num_frames();
1070 	cout << "Reading " << fname << " tiled"
1071 	     << (bycols ? ", by cols" : ", by rows") << " first" << endl;
1072 	// Figure #tiles in other dim.
1073 	int dim0_cnt = tiles;
1074 	int dim1_cnt = (nframes + dim0_cnt - 1) / dim0_cnt;
1075 	int needw;
1076 	int needh;       // Figure min. image dims.
1077 	if (bycols) {
1078 		needh = dim0_cnt * c_tilesize;
1079 		needw = dim1_cnt * c_tilesize;
1080 	} else {
1081 		needw = dim0_cnt * c_tilesize;
1082 		needh = dim1_cnt * c_tilesize;
1083 	}
1084 	int w;
1085 	int h;
1086 	int rowsize;
1087 	int xoff;
1088 	int yoff;
1089 	int palsize;
1090 	unsigned char *pixels;
1091 	unsigned char *oldpal;
1092 	// Import, with 255 = transp. index.
1093 	if (!Import_png8(fname, 255, w, h, rowsize, xoff, yoff,
1094 	                 pixels, oldpal, palsize)) {
1095 		Alert("Error reading '%s'", fname);
1096 		return;
1097 	}
1098 	// Convert to game palette.
1099 	Convert_indexed_image(pixels, h * rowsize, oldpal, palsize, pal);
1100 	delete [] oldpal;
1101 	if (w < needw || h < needh) {
1102 		Alert("File '%s' image is too small.  %dx%d required.",
1103 		      fname, needw, needh);
1104 		delete [] pixels;
1105 		return;
1106 	}
1107 	for (int frnum = 0; frnum < nframes; frnum++) {
1108 		int x;
1109 		int y;
1110 		if (bycols) {
1111 			y = frnum % dim0_cnt;
1112 			x = frnum / dim0_cnt;
1113 		} else {
1114 			x = frnum % dim0_cnt;
1115 			y = frnum / dim0_cnt;
1116 		}
1117 		unsigned char *src = pixels + w * c_tilesize * y + c_tilesize * x;
1118 		unsigned char buf[c_num_tile_bytes];    // Move tile to buffer.
1119 		unsigned char *ptr = &buf[0];
1120 		for (int row = 0; row < c_tilesize; row++) {
1121 			// Write it out.
1122 			memcpy(ptr, src, c_tilesize);
1123 			ptr += c_tilesize;
1124 			src += w;
1125 		}
1126 		shape->set_frame(make_unique<Shape_frame>(&buf[0], c_tilesize, c_tilesize,
1127 		                 c_tilesize, c_tilesize, false), frnum);
1128 	}
1129 	delete [] pixels;
1130 	finfo->set_modified();
1131 }
1132 
1133 /*
1134  *  Read in a shape that was changed by an external program (i.e., Gimp).
1135  */
1136 
read_back_edited(Editing_file * ed)1137 void Shape_chooser::read_back_edited(
1138     Editing_file *ed
1139 ) {
1140 	ExultStudio *studio = ExultStudio::get_instance();
1141 	Shape_file_info *finfo = studio->open_shape_file(
1142 	                             ed->vga_basename.c_str());
1143 	if (!finfo)
1144 		return;
1145 	unsigned char pal[3 * 256]; // Convert to 0-255 RGB's.
1146 	unsigned char *palbuf = studio->get_palbuf();
1147 	for (int i = 0; i < 3 * 256; i++)
1148 		pal[i] = palbuf[i] * 4;
1149 	if (!ed->tiles)
1150 		Import_png(ed->pathname.c_str(), finfo, pal,
1151 		           ed->shapenum, ed->framenum);
1152 	else
1153 		Import_png_tiles(ed->pathname.c_str(), finfo, pal,
1154 		                 ed->shapenum, ed->tiles, ed->bycolumns);
1155 }
1156 
1157 /*
1158  *  Delete all the files being edited after doing a final check.
1159  */
1160 
clear_editing_files()1161 void Shape_chooser::clear_editing_files(
1162 ) {
1163 	check_editing_files();      // Import any that changed.
1164 	for (auto &ed : editing_files) {
1165 		unlink(ed->pathname.c_str());
1166 	}
1167 	editing_files.clear();
1168 	if (check_editing_timer != -1)
1169 		g_source_remove(check_editing_timer);
1170 	check_editing_timer = -1;
1171 }
1172 
1173 /*
1174  *  Export current frame.
1175  */
1176 
export_frame(const char * fname,gpointer user_data)1177 void Shape_chooser::export_frame(
1178     const char *fname,
1179     gpointer user_data
1180 ) {
1181 	auto *chooser = static_cast<Shape_chooser *>(user_data);
1182 	if (U7exists(fname)) {
1183 		char *msg = g_strdup_printf(
1184 		                "'%s' already exists.  Overwrite?", fname);
1185 		int answer = Prompt(msg, "Yes", "No");
1186 		g_free(msg);
1187 		if (answer != 0)
1188 			return;
1189 	}
1190 	if (chooser->selected < 0)
1191 		return;         // Shouldn't happen.
1192 	chooser->export_png(fname);
1193 }
1194 
1195 /*
1196  *  Import current frame.
1197  */
1198 
import_frame(const char * fname,gpointer user_data)1199 void Shape_chooser::import_frame(
1200     const char *fname,
1201     gpointer user_data
1202 ) {
1203 	auto *chooser = static_cast<Shape_chooser *>(user_data);
1204 	if (chooser->selected < 0)
1205 		return;         // Shouldn't happen.
1206 	int shnum = chooser->info[chooser->selected].shapenum;
1207 	int frnum = chooser->info[chooser->selected].framenum;
1208 	unsigned char pal[3 * 256]; // Get current palette.
1209 	Get_rgb_palette(chooser->palette, pal);
1210 	Import_png(fname, chooser->file_info, pal, shnum, frnum);
1211 	chooser->render();
1212 	ExultStudio *studio = ExultStudio::get_instance();
1213 	studio->update_group_windows(nullptr);
1214 }
1215 
1216 /*
1217  *  Export all frames of current shape.
1218  */
1219 
export_all_pngs(const char * fname,int shnum)1220 void Shape_chooser::export_all_pngs(
1221     const char *fname,
1222     int shnum
1223 ) {
1224 	for (int i = 0; i < ifile->get_num_frames(shnum); i++) {
1225 		char *fullname = new char[strlen(fname) + 30];
1226 		sprintf(fullname, "%s%02d.png", fname, i);
1227 		cout << "Writing " << fullname << endl;
1228 		Shape_frame *frame = ifile->get_shape(shnum, i);
1229 		int w = frame->get_width();
1230 		int h = frame->get_height();
1231 		Image_buffer8 img(w, h);    // Render into a buffer.
1232 		img.fill8(transp);      // Fill with transparent pixel.
1233 		frame->paint(&img, frame->get_xleft(), frame->get_yabove());
1234 		int xoff = 0;
1235 		int yoff = 0;
1236 		if (frame->is_rle()) {
1237 			xoff = -frame->get_xright();
1238 			yoff = -frame->get_ybelow();
1239 		}
1240 		export_png(fullname, img, xoff, yoff);
1241 		delete [] fullname;
1242 	}
1243 }
1244 
export_all_frames(const char * fname,gpointer user_data)1245 void Shape_chooser::export_all_frames(
1246     const char *fname,
1247     gpointer user_data
1248 ) {
1249 	auto *chooser = static_cast<Shape_chooser *>(user_data);
1250 	int shnum = chooser->info[chooser->selected].shapenum;
1251 	chooser->export_all_pngs(fname, shnum);
1252 }
1253 
export_shape(const char * fname,gpointer user_data)1254 void Shape_chooser::export_shape(
1255     const char *fname,
1256     gpointer user_data
1257 ) {
1258 	auto *chooser = static_cast<Shape_chooser *>(user_data);
1259 	int shnum = chooser->info[chooser->selected].shapenum;
1260 	Shape *shp = chooser->ifile->extract_shape(shnum);
1261 	Image_file_info::write_file(fname, &shp, 1, true);
1262 }
1263 
1264 /*
1265  *  Import all frames into current shape.
1266  */
1267 
import_all_pngs(const char * fname,int shnum)1268 void Shape_chooser::import_all_pngs(
1269     const char *fname,
1270     int shnum
1271 ) {
1272 	char *fullname = new char[strlen(fname) + 30];
1273 	sprintf(fullname, "%s%02d.png", fname, 0);
1274 	if (!U7exists(fullname)) {
1275 		cerr << "Invalid base file name for import of all frames!" << endl;
1276 		delete [] fullname;
1277 		return;
1278 	}
1279 	int i = 0;
1280 	unsigned char pal[3 * 256]; // Get current palette.
1281 	Get_rgb_palette(palette, pal);
1282 	Shape *shape = ifile->extract_shape(shnum);
1283 	if (!shape) {
1284 		delete [] fullname;
1285 		return;
1286 	}
1287 	ExultStudio *studio = ExultStudio::get_instance();
1288 	while (U7exists(fullname)) {
1289 		int w;
1290 		int h;
1291 		int rowsize;
1292 		int xoff;
1293 		int yoff;
1294 		int palsize;
1295 		unsigned char *pixels;
1296 		unsigned char *oldpal;
1297 		// Import, with 255 = transp. index.
1298 		if (!Import_png8(fullname, 255, w, h, rowsize, xoff, yoff,
1299 		                 pixels, oldpal, palsize)) {
1300 			delete [] fullname;
1301 			return;         // Just return if error, for now.
1302 		}
1303 		// Convert to game palette.
1304 		Convert_indexed_image(pixels, h * rowsize, oldpal, palsize, pal);
1305 		delete [] oldpal;
1306 		int xleft = w + xoff - 1;
1307 		int yabove = h + yoff - 1;
1308 		auto frame = make_unique<Shape_frame>(pixels,
1309 		                                      w, h, xleft, yabove, true);
1310 		if (i < ifile->get_num_frames(shnum))
1311 			shape->set_frame(std::move(frame), i);
1312 		else
1313 			shape->add_frame(std::move(frame), i);
1314 		delete [] pixels;
1315 
1316 		i++;
1317 		sprintf(fullname, "%s%02d.png", fname, i);
1318 	}
1319 	render();
1320 	file_info->set_modified();
1321 	studio->update_group_windows(nullptr);
1322 	delete [] fullname;
1323 }
1324 
import_all_frames(const char * fname,gpointer user_data)1325 void Shape_chooser::import_all_frames(
1326     const char *fname,
1327     gpointer user_data
1328 ) {
1329 	auto *chooser = static_cast<Shape_chooser *>(user_data);
1330 	if (chooser->selected < 0)
1331 		return;         // Shouldn't happen.
1332 	int shnum = chooser->info[chooser->selected].shapenum;
1333 	int len = strlen(fname);
1334 	// Ensure we have a valid file name.
1335 	std::string file = fname;
1336 	if (file[len - 4] == '.')
1337 		file[len - 6] = 0;
1338 	chooser->import_all_pngs(file.c_str(), shnum);
1339 }
1340 
import_shape(const char * fname,gpointer user_data)1341 void Shape_chooser::import_shape(
1342     const char *fname,
1343     gpointer user_data
1344 ) {
1345 	if (U7exists(fname)) {
1346 		auto *chooser = static_cast<Shape_chooser *>(user_data);
1347 		if (chooser->selected < 0)
1348 			return;         // Shouldn't happen.
1349 		IFileDataSource ds(fname);
1350 		// Check to see if it is a valid shape file.
1351 		// We never get here through a flat, so we don't deal
1352 		// with that case. These tests aren't perfect!
1353 		const int size = ds.getSize();
1354 		const int len = ds.read4();
1355 		int first;
1356 		if (len != size || (first = ds.read4()) > size || (first % 4) != 0)
1357 			return;
1358 		int shnum = chooser->info[chooser->selected].shapenum;
1359 		Shape *shp = chooser->ifile->extract_shape(shnum);
1360 		ds.seek(0);
1361 		shp->load(&ds);
1362 		shp->set_modified();
1363 		chooser->render();
1364 		chooser->file_info->set_modified();
1365 	}
1366 }
1367 
1368 /*
1369  *  Add a frame.
1370  */
1371 
new_frame()1372 void Shape_chooser::new_frame(
1373 ) {
1374 	if (selected < 0)
1375 		return;
1376 	int shnum = info[selected].shapenum;
1377 	int frnum = info[selected].framenum;
1378 	Vga_file *ifile = file_info->get_ifile();
1379 	// Read entire shape.
1380 	Shape *shape = ifile->extract_shape(shnum);
1381 	if (!shape ||           // Shouldn't happen.
1382 	        // We'll insert AFTER frnum.
1383 	        frnum > shape->get_num_frames())
1384 		return;
1385 	// Low shape in 'shapes.vga'?
1386 	ExultStudio *studio = ExultStudio::get_instance();
1387 	bool flat = IS_FLAT(shnum) && file_info == studio->get_vgafile();
1388 	int w = 0;
1389 	int h = 0;
1390 	int xleft;
1391 	int yabove;
1392 	if (flat)
1393 		w = h = xleft = yabove = c_tilesize;
1394 	else {              // Find largest frame.
1395 		int cnt = shape->get_num_frames();
1396 		for (int i = 0; i < cnt; i++) {
1397 			Shape_frame *fr = shape->get_frame(i);
1398 			if (!fr) {
1399 				continue;
1400 			}
1401 			int ht = fr->get_height();
1402 			if (ht > h)
1403 				h = ht;
1404 			int wd = fr->get_width();
1405 			if (wd > w)
1406 				w = wd;
1407 		}
1408 		if (h == 0)
1409 			h = c_tilesize;
1410 		if (w == 0)
1411 			w = c_tilesize;
1412 		xleft = w - 1;
1413 		yabove = h - 1;
1414 	}
1415 	Image_buffer8 img(w, h);
1416 	img.fill8(1);           // Just use color #1.
1417 	if (w > 2 && h > 2)
1418 		img.fill8(2, w - 2, h - 2, 1, 1);
1419 	shape->add_frame(make_unique<Shape_frame>(img.get_bits(),
1420 	                 w, h, xleft, yabove, !flat), frnum + 1);
1421 	file_info->set_modified();
1422 	Object_browser *browser = studio->get_browser();
1423 	if (browser) {
1424 		// Repaint main window.
1425 		if (frames_mode)
1426 			browser->setup_info(true);
1427 		browser->render();
1428 	}
1429 	studio->update_group_windows(nullptr);
1430 }
1431 
1432 /*
1433  *  Callback for new-shape 'okay'.
1434  */
1435 C_EXPORT void
on_new_shape_okay_clicked(GtkButton * button,gpointer user_data)1436 on_new_shape_okay_clicked(GtkButton       *button,
1437                           gpointer         user_data) {
1438 	ignore_unused_variable_warning(user_data);
1439 	GtkWidget *win = gtk_widget_get_toplevel(GTK_WIDGET(button));
1440 	auto *chooser = static_cast<Shape_chooser *>(
1441 	                    g_object_get_data(G_OBJECT(win), "user_data"));
1442 	chooser->create_new_shape();
1443 	gtk_widget_hide(win);
1444 }
1445 // Toggled 'From font' button:
on_new_shape_font_toggled(GtkToggleButton * btn,gpointer user_data)1446 C_EXPORT void on_new_shape_font_toggled(
1447     GtkToggleButton *btn,
1448     gpointer user_data
1449 ) {
1450 	ignore_unused_variable_warning(user_data);
1451 	bool on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn));
1452 	GtkWidget *win = gtk_widget_get_toplevel(GTK_WIDGET(btn));
1453 	auto *chooser = static_cast<Shape_chooser *>(
1454 	                    g_object_get_data(G_OBJECT(win), "user_data"));
1455 	chooser->from_font_toggled(on);
1456 }
on_new_shape_font_color_draw_expose_event(GtkWidget * widget,cairo_t * cairo,gpointer data)1457 gboolean Shape_chooser::on_new_shape_font_color_draw_expose_event(
1458     GtkWidget *widget,      // The draw area.
1459     cairo_t *cairo,
1460     gpointer data           // -> Shape_chooser.
1461 ) {
1462 	ignore_unused_variable_warning(data);
1463 	ExultStudio *studio = ExultStudio::get_instance();
1464 	int index = studio->get_spin("new_shape_font_color");
1465 	auto *chooser = static_cast<Shape_chooser *>(
1466 	               g_object_get_data(G_OBJECT(widget), "user_data"));
1467 	guint32 color = chooser->get_color(index);
1468 	GdkRectangle area = { 0, 0, 0, 0 };
1469 	gdk_cairo_get_clip_rectangle(cairo, &area);
1470 	cairo_set_source_rgb(cairo,
1471 	                     ((color >> 16) & 255) / 255.0,
1472 	                     ((color >> 8) & 255) / 255.0,
1473 	                     (color & 255) / 255.0);
1474 	cairo_rectangle(cairo, area.x, area.y, area.width, area.height);
1475 	cairo_fill(cairo);
1476 	return TRUE;
1477 }
1478 
on_new_shape_font_color_changed(GtkSpinButton * button,gpointer user_data)1479 C_EXPORT void on_new_shape_font_color_changed(
1480     GtkSpinButton *button,
1481     gpointer user_data
1482 ) {
1483 	ignore_unused_variable_warning(button, user_data);
1484 	ExultStudio *studio = ExultStudio::get_instance();
1485 	// Show new color.
1486 	gtk_widget_queue_draw(studio->get_widget("new_shape_font_color_draw"));
1487 }
1488 
1489 /*
1490  *  Font file was selected.
1491  */
1492 
font_file_chosen(const char * fname,gpointer user_data)1493 static void font_file_chosen(
1494     const char *fname,
1495     gpointer user_data
1496 ) {
1497 	ignore_unused_variable_warning(user_data);
1498 	ExultStudio *studio = ExultStudio::get_instance();
1499 	studio->set_entry("new_shape_font_name", fname);
1500 	studio->set_spin("new_shape_nframes", 128);
1501 }
1502 
1503 /*
1504  *  'From font' toggled in 'New shape' dialog.
1505  */
1506 
from_font_toggled(bool on)1507 void Shape_chooser::from_font_toggled(
1508     bool on
1509 ) {
1510 	ExultStudio *studio = ExultStudio::get_instance();
1511 	studio->set_sensitive("new_shape_font_name", on);
1512 	if (!on)
1513 		return;
1514 	studio->set_sensitive("new_shape_font_color", true);
1515 	studio->set_sensitive("new_shape_font_height", true);
1516 	Create_file_selection("Choose font file",
1517 	                      "<PATCH>", "Fonts",
1518 	                      {"*.ttf", "*.ttc", "*.otf",
1519 	                       "*.otc", "*.pcf", "*.fnt", "*.fon"},
1520 	                      GTK_FILE_CHOOSER_ACTION_OPEN,
1521 	                      font_file_chosen,
1522 	                      nullptr);
1523 }
1524 
1525 /*
1526  *  Add a new shape.
1527  */
1528 
new_shape()1529 void Shape_chooser::new_shape(
1530 ) {
1531 	ExultStudio *studio = ExultStudio::get_instance();
1532 	GtkWidget *win = studio->get_widget("new_shape_window");
1533 	gtk_window_set_modal(GTK_WINDOW(win), true);
1534 	g_object_set_data(G_OBJECT(win), "user_data", this);
1535 	// Get current selection.
1536 	int shnum = selected >= 0 ? info[selected].shapenum : 0;
1537 	GtkWidget *spin = studio->get_widget("new_shape_num");
1538 	GtkAdjustment *adj = gtk_spin_button_get_adjustment(
1539 	                         GTK_SPIN_BUTTON(spin));
1540 	gtk_adjustment_set_upper(adj, c_max_shapes - 1);
1541 	Vga_file *ifile = file_info->get_ifile();
1542 	int shstart;            // Find an unused shape.
1543 	for (shstart = shnum; shstart <= gtk_adjustment_get_upper(adj); shstart++)
1544 		if (shstart >= ifile->get_num_shapes() ||
1545 		        !ifile->get_num_frames(shstart))
1546 			break;
1547 	if (shstart > gtk_adjustment_get_upper(adj)) {
1548 		for (shstart = shnum - 1; shstart >= 0; shstart--)
1549 			if (!ifile->get_num_frames(shstart))
1550 				break;
1551 		if (shstart < 0)
1552 			shstart = shnum;
1553 	}
1554 	gtk_adjustment_set_value(adj, shstart);
1555 	spin = studio->get_widget("new_shape_nframes");
1556 	adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
1557 	bool flat = IS_FLAT(shnum) && file_info == studio->get_vgafile();
1558 	if (flat)
1559 		gtk_adjustment_set_upper(adj, 31);
1560 	else
1561 		gtk_adjustment_set_upper(adj, 255);
1562 	spin = studio->get_widget("new_shape_font_height");
1563 	studio->set_sensitive("new_shape_font_height", false);
1564 	adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
1565 	gtk_adjustment_set_lower(adj, 4);
1566 	gtk_adjustment_set_upper(adj, 64);
1567 	spin = studio->get_widget("new_shape_font_color");
1568 	studio->set_sensitive("new_shape_font_color", false);
1569 	adj = gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(spin));
1570 	gtk_adjustment_set_lower(adj, 0);
1571 	gtk_adjustment_set_upper(adj, 255);
1572 	// Unset 'From font:'.
1573 	studio->set_toggle("new_shape_font", false);
1574 #ifndef HAVE_FREETYPE2          /* No freetype?  No fonts.  */
1575 	studio->set_sensitive("new_shape_font", false);
1576 #endif
1577 	// Store our pointer in color drawer.
1578 	GtkWidget *draw = studio->get_widget("new_shape_font_color_draw");
1579 	g_object_set_data(G_OBJECT(draw), "user_data", this);
1580 	g_signal_connect(G_OBJECT(draw), "draw",
1581 	                 G_CALLBACK(on_new_shape_font_color_draw_expose_event), this);
1582 	gtk_widget_show(win);
1583 }
1584 
1585 /*
1586  *  Add a new shape after the user has clicked 'Okay' in the new-shape
1587  *  dialog.
1588  */
1589 
create_new_shape()1590 void Shape_chooser::create_new_shape(
1591 ) {
1592 	ExultStudio *studio = ExultStudio::get_instance();
1593 	int shnum = studio->get_spin("new_shape_num");
1594 	int nframes = studio->get_spin("new_shape_nframes");
1595 	if (nframes <= 0)
1596 		nframes = 1;
1597 	else if (nframes > 256)
1598 		nframes = 256;
1599 	Vga_file *ifile = file_info->get_ifile();
1600 	if (shnum < ifile->get_num_shapes() && ifile->get_num_frames(shnum)) {
1601 		if (Prompt("Replace existing shape?", "Yes", "No") != 0)
1602 			return;
1603 	}
1604 	Shape *shape = ifile->new_shape(shnum);
1605 	if (!shape) {
1606 		Alert("Can't create shape %d", shnum);
1607 		return;
1608 	}
1609 	// Create frames.
1610 	bool flat = IS_FLAT(shnum) && file_info == studio->get_vgafile();
1611 	bool use_font = false;
1612 #ifdef HAVE_FREETYPE2
1613 	// Want to create from a font?
1614 	use_font = studio->get_toggle("new_shape_font");
1615 	const char *fontname = studio->get_text_entry("new_shape_font_name");
1616 	use_font = use_font && (fontname != nullptr) && *fontname != 0;
1617 	if (use_font) {
1618 		if (flat) {
1619 			Alert("Can't load font into a 'flat' shape");
1620 			return;
1621 		}
1622 		int ht = studio->get_spin("new_shape_font_height");
1623 		int fg = studio->get_spin("new_shape_font_color");
1624 		if (!Gen_font_shape(shape, fontname, nframes,
1625 		                    // Use transparent color for bgnd.
1626 		                    ht, fg, 255))
1627 			Alert("Error loading font file '%s'", fontname);
1628 	}
1629 #endif
1630 	if (!use_font) {
1631 		int w = c_tilesize;
1632 		int h = c_tilesize;
1633 		int xleft = flat ? c_tilesize : w - 1;
1634 		int yabove = flat ? c_tilesize : h - 1;
1635 		Image_buffer8 img(w, h);
1636 		img.fill8(1);       // Just use color #1.
1637 		img.fill8(2, w - 2, h - 2, 1, 1);
1638 		// Include some transparency.
1639 		img.fill8(255, w / 2, h / 2, w / 4, h / 4);
1640 		for (int i = 0; i < nframes; i++)
1641 			shape->add_frame(make_unique<Shape_frame>(img.get_bits(),
1642 			                 w, h, xleft, yabove, !flat), i);
1643 	}
1644 	file_info->set_modified();
1645 	Object_browser *browser = studio->get_browser();
1646 	if (browser) {
1647 		// Repaint main window.
1648 		browser->setup_info(true);
1649 		browser->render();
1650 	}
1651 	studio->update_group_windows(nullptr);
1652 }
1653 
1654 /*
1655  *  Delete a frame, and the shape itself if this is its last.
1656  */
1657 
del_frame()1658 void Shape_chooser::del_frame(
1659 ) {
1660 	if (selected < 0)
1661 		return;
1662 	int shnum = info[selected].shapenum;
1663 	int frnum = info[selected].framenum;
1664 	Vga_file *ifile = file_info->get_ifile();
1665 	// Read entire shape.
1666 	Shape *shape = ifile->extract_shape(shnum);
1667 	if (!shape ||           // Shouldn't happen.
1668 	        frnum > shape->get_num_frames() - 1)
1669 		return;
1670 	// 1-shape file & last frame?
1671 	if (!ifile->is_flex() && shape->get_num_frames() == 1)
1672 		return;
1673 	shape->del_frame(frnum);
1674 	file_info->set_modified();
1675 	ExultStudio *studio = ExultStudio::get_instance();
1676 	Object_browser *browser = studio->get_browser();
1677 	if (browser) {
1678 		// Repaint main window.
1679 		browser->render();
1680 	}
1681 	studio->update_group_windows(nullptr);
1682 }
1683 
1684 /*
1685  *  Someone wants the dragged shape.
1686  */
1687 
drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * seldata,guint info,guint time,gpointer data)1688 void Shape_chooser::drag_data_get(
1689     GtkWidget *widget,      // The view window.
1690     GdkDragContext *context,
1691     GtkSelectionData *seldata,  // Fill this in.
1692     guint info,
1693     guint time,
1694     gpointer data           // ->Shape_chooser.
1695 ) {
1696 	ignore_unused_variable_warning(widget, context, time);
1697 	cout << "In DRAG_DATA_GET of Shape for '"
1698 	     << gdk_atom_name(gtk_selection_data_get_target(seldata))
1699 	     << "'" << endl;
1700 	auto *chooser = static_cast<Shape_chooser *>(data);
1701 	if (chooser->selected < 0 || info != U7_TARGET_SHAPEID)
1702 		return;         // Not sure about this.
1703 	guchar buf[U7DND_DATA_LENGTH(3)];
1704 	int file = chooser->ifile->get_u7drag_type();
1705 	if (file == U7_SHAPE_UNK)
1706 		file = U7_SHAPE_SHAPES; // Just assume it's shapes.vga.
1707 	Shape_entry &shinfo = chooser->info[chooser->selected];
1708 	int len = Store_u7_shapeid(buf, file, shinfo.shapenum,
1709 	                           shinfo.framenum);
1710 	cout << "Setting selection data (" << shinfo.shapenum <<
1711 	     '/' << shinfo.framenum << ')' << endl;
1712 	// Set data.
1713 	gtk_selection_data_set(seldata,
1714 	                       gtk_selection_data_get_target(seldata),
1715 	                       8, buf, len);
1716 }
1717 
1718 /*
1719  *  Beginning of a drag.
1720  */
1721 
drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer data)1722 gint Shape_chooser::drag_begin(
1723     GtkWidget *widget,      // The view window.
1724     GdkDragContext *context,
1725     gpointer data           // ->Shape_chooser.
1726 ) {
1727 	ignore_unused_variable_warning(widget);
1728 	cout << "In DRAG_BEGIN of Shape" << endl;
1729 	auto *chooser = static_cast<Shape_chooser *>(data);
1730 	if (chooser->selected < 0)
1731 		return FALSE;       // ++++Display a halt bitmap.
1732 	// Get ->shape.
1733 	Shape_entry &shinfo = chooser->info[chooser->selected];
1734 	Shape_frame *shape = chooser->ifile->get_shape(shinfo.shapenum,
1735 	                     shinfo.framenum);
1736 	if (!shape)
1737 		return FALSE;
1738 	chooser->set_drag_icon(context, shape); // Set icon for dragging.
1739 	return TRUE;
1740 }
1741 
1742 /*
1743  *  Scroll to a new shape/frame.
1744  */
1745 
scroll_row_vertical(unsigned newrow)1746 void Shape_chooser::scroll_row_vertical(
1747     unsigned newrow          // Abs. index of row to show.
1748 ) {
1749 	if (newrow >= rows.size())
1750 		return;
1751 	row0 = newrow;
1752 	row0_voffset = 0;
1753 	render();
1754 }
1755 
1756 /*
1757  *  Scroll to new pixel offset.
1758  */
1759 
scroll_vertical(int newoffset)1760 void Shape_chooser::scroll_vertical(
1761     int newoffset
1762 ) {
1763 	int delta = newoffset - voffset;
1764 	while (delta > 0 && row0 < rows.size() - 1) {
1765 		// Going down.
1766 		int rowh = rows[row0].height - row0_voffset;
1767 		if (delta < rowh) {
1768 			// Part of current row.
1769 			voffset += delta;
1770 			row0_voffset += delta;
1771 			delta = 0;
1772 		} else {
1773 			// Go down to next row.
1774 			voffset += rowh;
1775 			delta -= rowh;
1776 			++row0;
1777 			row0_voffset = 0;
1778 		}
1779 	}
1780 	while (delta < 0) {
1781 		if (-delta <= row0_voffset) {
1782 			voffset += delta;
1783 			row0_voffset += delta;
1784 			delta = 0;
1785 		} else if (row0_voffset) {
1786 			voffset -= row0_voffset;
1787 			delta += row0_voffset;
1788 			row0_voffset = 0;
1789 		} else {
1790 			if (row0 == 0)
1791 				break;
1792 			--row0;
1793 			row0_voffset = 0;
1794 			voffset -= rows[row0].height;
1795 			delta += rows[row0].height;
1796 			if (delta > 0) {
1797 				row0_voffset = delta;
1798 				voffset += delta;
1799 				delta = 0;
1800 			}
1801 		}
1802 	}
1803 	render();
1804 	update_statusbar();
1805 }
1806 
1807 /*
1808  *  Adjust vertical scroll amounts after laying out shapes.
1809  */
1810 
setup_vscrollbar()1811 void Shape_chooser::setup_vscrollbar(
1812 ) {
1813 	GtkAdjustment *adj = gtk_range_get_adjustment(
1814 	                         GTK_RANGE(vscroll));
1815 	gtk_adjustment_set_value(adj, 0);
1816 	gtk_adjustment_set_lower(adj, 0);
1817 	gtk_adjustment_set_upper(adj, total_height);
1818 	gtk_adjustment_set_step_increment(adj, 16);   // +++++FOR NOW.
1819 	gtk_adjustment_set_page_increment(adj, config_height);
1820 	gtk_adjustment_set_page_size(adj, config_height);
1821 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
1822 }
1823 
1824 /*
1825  *  Adjust horizontal scroll amounts.
1826  */
1827 
setup_hscrollbar(int newmax)1828 void Shape_chooser::setup_hscrollbar(
1829     int newmax          // New max., or -1 to leave alone.
1830 ) {
1831 	GtkAdjustment *adj = gtk_range_get_adjustment(
1832 	                         GTK_RANGE(hscroll));
1833 	if (newmax > 0)
1834 		gtk_adjustment_set_upper(adj, newmax);
1835 	GtkAllocation alloc = {0, 0, 0, 0};
1836 	gtk_widget_get_allocation(draw, &alloc);
1837 	gtk_adjustment_set_page_increment(adj, alloc.width);
1838 	gtk_adjustment_set_page_size(adj, alloc.width);
1839 	if (gtk_adjustment_get_page_size(adj) > gtk_adjustment_get_upper(adj))
1840 		gtk_adjustment_set_upper(adj, gtk_adjustment_get_page_size(adj));
1841 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
1842 }
1843 
1844 /*
1845  *  Handle a scrollbar event.
1846  */
1847 
vscrolled(GtkAdjustment * adj,gpointer data)1848 void Shape_chooser::vscrolled(      // For vertical scrollbar.
1849     GtkAdjustment *adj,     // The adjustment.
1850     gpointer data           // ->Shape_chooser.
1851 ) {
1852 	auto *chooser = static_cast<Shape_chooser *>(data);
1853 	cout << "Scrolled to " << gtk_adjustment_get_value(adj) << '\n';
1854 	gint newindex = static_cast<gint>(gtk_adjustment_get_value(adj));
1855 	chooser->scroll_vertical(newindex);
1856 }
hscrolled(GtkAdjustment * adj,gpointer data)1857 void Shape_chooser::hscrolled(      // For horizontal scrollbar.
1858     GtkAdjustment *adj,     // The adjustment.
1859     gpointer data           // ->Shape_chooser.
1860 ) {
1861 	auto *chooser = static_cast<Shape_chooser *>(data);
1862 	chooser->hoffset = static_cast<gint>(gtk_adjustment_get_value(adj));
1863 	chooser->render();
1864 }
1865 
1866 /*
1867  *  Handle a change to the 'frame' spin button.
1868  */
1869 
frame_changed(GtkAdjustment * adj,gpointer data)1870 void Shape_chooser::frame_changed(
1871     GtkAdjustment *adj,     // The adjustment.
1872     gpointer data           // ->Shape_chooser.
1873 ) {
1874 	auto *chooser = static_cast<Shape_chooser *>(data);
1875 	gint newframe = static_cast<gint>(gtk_adjustment_get_value(adj));
1876 	if (chooser->selected >= 0) {
1877 		Shape_entry &shinfo = chooser->info[chooser->selected];
1878 		int nframes = chooser->ifile->get_num_frames(shinfo.shapenum);
1879 		if (newframe >= nframes)    // Just checking
1880 			return;
1881 		shinfo.framenum = newframe;
1882 		if (chooser->frames_mode)   // Get sel. frame in view.
1883 			chooser->scroll_to_frame();
1884 		chooser->render();
1885 		chooser->update_statusbar();
1886 	}
1887 }
1888 
1889 /*
1890  *  'All frames' toggled.
1891  */
1892 
all_frames_toggled(GtkToggleButton * btn,gpointer data)1893 void Shape_chooser::all_frames_toggled(
1894     GtkToggleButton *btn,
1895     gpointer data
1896 ) {
1897 	auto *chooser = static_cast<Shape_chooser *>(data);
1898 	bool on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn));
1899 	chooser->frames_mode = on;
1900 	if (on)             // Frame => show horiz. scrollbar.
1901 		gtk_widget_show(chooser->hscroll);
1902 	else
1903 		gtk_widget_hide(chooser->hscroll);
1904 	// The old index is no longer valid, so we need to remember the shape.
1905 	int indx = chooser->selected >= 0 ? chooser->selected
1906 	           : static_cast<int>(chooser->rows[chooser->row0].index0);
1907 	int shnum = chooser->info[indx].shapenum;
1908 	chooser->selected = -1;
1909 	chooser->setup_info();
1910 	indx = chooser->find_shape(shnum);
1911 	if (indx >= 0)          // Get back to given shape.
1912 		chooser->goto_index(indx);
1913 }
1914 
1915 /*
1916  *  Handle popup menu items.
1917  */
1918 
on_shapes_popup_info_activate(GtkMenuItem * item,gpointer udata)1919 void Shape_chooser::on_shapes_popup_info_activate(
1920     GtkMenuItem *item,
1921     gpointer udata
1922 ) {
1923 	ignore_unused_variable_warning(item);
1924 	static_cast<Shape_chooser *>(udata)->edit_shape_info();
1925 }
1926 
on_shapes_popup_edit_activate(GtkMenuItem * item,gpointer udata)1927 void Shape_chooser::on_shapes_popup_edit_activate(
1928     GtkMenuItem *item,
1929     gpointer udata
1930 ) {
1931 	ignore_unused_variable_warning(item);
1932 	static_cast<Shape_chooser *>(udata)->edit_shape();
1933 }
1934 
on_shapes_popup_edtiles_activate(GtkMenuItem * item,gpointer udata)1935 void Shape_chooser::on_shapes_popup_edtiles_activate(
1936     GtkMenuItem *item,
1937     gpointer udata
1938 ) {
1939 	ignore_unused_variable_warning(item);
1940 	auto *ch = static_cast<Shape_chooser *>(udata);
1941 	if (ch->selected < 0)
1942 		return;         // Shouldn't happen.
1943 	ExultStudio *studio = ExultStudio::get_instance();
1944 	GtkWidget *win = studio->get_widget("export_tiles_window");
1945 	gtk_window_set_modal(GTK_WINDOW(win), true);
1946 	g_object_set_data(G_OBJECT(win), "user_data", ch);
1947 	// Get current selection.
1948 	int shnum = ch->info[ch->selected].shapenum;
1949 	Vga_file *ifile = ch->file_info->get_ifile();
1950 	int nframes = ifile->get_num_frames(shnum);
1951 	GtkWidget *spin = studio->get_widget("export_tiles_count");
1952 	GtkAdjustment *adj = gtk_spin_button_get_adjustment(
1953 	                         GTK_SPIN_BUTTON(spin));
1954 	gtk_adjustment_set_lower(adj, 1);
1955 	gtk_adjustment_set_upper(adj, nframes);
1956 	gtk_widget_show(win);
1957 }
1958 
on_shapes_popup_import(GtkMenuItem * item,gpointer udata)1959 static void on_shapes_popup_import(
1960     GtkMenuItem *item,
1961     gpointer udata
1962 ) {
1963 	ignore_unused_variable_warning(item);
1964 	Create_file_selection("Import frame from a .png file",
1965 	                      "<PATCH>", "PNG Images",
1966 	                      {"*.png"},
1967 	                      GTK_FILE_CHOOSER_ACTION_OPEN,
1968 	                      Shape_chooser::import_frame,
1969 	                      udata);
1970 }
on_shapes_popup_export(GtkMenuItem * item,gpointer udata)1971 static void on_shapes_popup_export(
1972     GtkMenuItem *item,
1973     gpointer udata
1974 ) {
1975 	ignore_unused_variable_warning(item);
1976 	Create_file_selection("Export frame to a .png file",
1977 	                      "<PATCH>", "PNG Images",
1978 	                      {"*.png"},
1979 	                      GTK_FILE_CHOOSER_ACTION_SAVE,
1980 	                      Shape_chooser::export_frame,
1981 	                      udata);
1982 }
on_shapes_popup_export_all(GtkMenuItem * item,gpointer udata)1983 static void on_shapes_popup_export_all(
1984     GtkMenuItem *item,
1985     gpointer udata
1986 ) {
1987 	ignore_unused_variable_warning(item);
1988 	Create_file_selection("Choose the base .png file name for all frames",
1989 	                      "<PATCH>", nullptr,
1990 	                      {},
1991 	                      GTK_FILE_CHOOSER_ACTION_SAVE,
1992 	                      Shape_chooser::export_all_frames,
1993 	                      udata);
1994 }
on_shapes_popup_import_all(GtkMenuItem * item,gpointer udata)1995 static void on_shapes_popup_import_all(
1996     GtkMenuItem *item,
1997     gpointer udata
1998 ) {
1999 	ignore_unused_variable_warning(item);
2000 	Create_file_selection("Choose the one of the .png sprites to import",
2001 	                      "<PATCH>", "PNG Images",
2002 	                      {"*.png"},
2003 	                      GTK_FILE_CHOOSER_ACTION_OPEN,
2004 	                      Shape_chooser::import_all_frames,
2005 	                      udata);
2006 }
on_shapes_popup_export_shape(GtkMenuItem * item,gpointer udata)2007 static void on_shapes_popup_export_shape(
2008     GtkMenuItem *item,
2009     gpointer udata
2010 ) {
2011 	ignore_unused_variable_warning(item);
2012 	Create_file_selection("Choose the shp file name",
2013 	                      "<PATCH>", "Shape files",
2014 	                      {"*.shp"},
2015 	                      GTK_FILE_CHOOSER_ACTION_SAVE,
2016 	                      Shape_chooser::export_shape,
2017 	                      udata);
2018 }
on_shapes_popup_import_shape(GtkMenuItem * item,gpointer udata)2019 static void on_shapes_popup_import_shape(
2020     GtkMenuItem *item,
2021     gpointer udata
2022 ) {
2023 	ignore_unused_variable_warning(item);
2024 	Create_file_selection("Choose the shp file to import",
2025 	                      "<PATCH>", "Shape files",
2026 	                      {"*.shp"},
2027 	                      GTK_FILE_CHOOSER_ACTION_OPEN,
2028 	                      Shape_chooser::import_shape,
2029 	                      udata);
2030 }
on_shapes_popup_new_frame(GtkMenuItem * item,gpointer udata)2031 static void on_shapes_popup_new_frame(
2032     GtkMenuItem *item,
2033     gpointer udata
2034 ) {
2035 	ignore_unused_variable_warning(item);
2036 	static_cast<Shape_chooser *>(udata)->new_frame();
2037 }
on_shapes_popup_new_shape(GtkMenuItem * item,gpointer udata)2038 static void on_shapes_popup_new_shape(
2039     GtkMenuItem *item,
2040     gpointer udata
2041 ) {
2042 	ignore_unused_variable_warning(item);
2043 	static_cast<Shape_chooser *>(udata)->new_shape();
2044 }
2045 
2046 /*
2047  *  Callback for edit-tiles 'okay'.
2048  */
2049 C_EXPORT void
on_export_tiles_okay_clicked(GtkButton * button,gpointer user_data)2050 on_export_tiles_okay_clicked(GtkButton       *button,
2051                              gpointer         user_data) {
2052 	ignore_unused_variable_warning(user_data);
2053 	GtkWidget *win = gtk_widget_get_toplevel(GTK_WIDGET(button));
2054 	auto *chooser = static_cast<Shape_chooser *>(
2055 	                    g_object_get_data(G_OBJECT(win), "user_data"));
2056 	ExultStudio *studio = ExultStudio::get_instance();
2057 	int tiles = studio->get_spin("export_tiles_count");
2058 	bool bycol = studio->get_toggle("tiled_by_columns");
2059 	chooser->edit_shape(tiles, bycol);
2060 	gtk_widget_hide(win);
2061 }
2062 
2063 /*
2064  *  Handle a shape dropped on our draw area.
2065  */
2066 
shape_dropped_here(int file,int shape,int frame)2067 void Shape_chooser::shape_dropped_here(
2068     int file,           // U7_SHAPE_SHAPES.
2069     int shape,
2070     int frame
2071 ) {
2072 	ignore_unused_variable_warning(frame);
2073 	// Got to be from same file type.
2074 	if (ifile->get_u7drag_type() == file && group != nullptr) {
2075 		// Add to group.
2076 		if (group->is_builtin()) {
2077 			Alert("Can't modify builtin group.");
2078 			return;
2079 		}
2080 		group->add(shape);
2081 		// Update all windows for this group.
2082 		ExultStudio::get_instance()->update_group_windows(group);
2083 	}
2084 }
2085 
2086 /*
2087  *  Get # shapes we can display.
2088  */
2089 
get_count()2090 int Shape_chooser::get_count(
2091 ) {
2092 	return group ? group->size() : ifile->get_num_shapes();
2093 }
2094 
2095 /*
2096  *  Search for an entry.
2097  */
2098 
search(const char * search,int dir)2099 void Shape_chooser::search(
2100     const char *search,     // What to search for.
2101     int dir             // 1 or -1.
2102 ) {
2103 	if (!shapes_file)       // Not 'shapes.vga'.
2104 		return;         // In future, maybe find shape #?
2105 	int total = get_count();
2106 	if (!total)
2107 		return;         // Empty.
2108 	// Read info if not read.
2109 	ExultStudio *studio = ExultStudio::get_instance();
2110 	if (shapes_file->read_info(studio->get_game_type(), true))
2111 		studio->set_shapeinfo_modified();
2112 	// Start with selection, or top.
2113 	int start = selected >= 0 ? selected : static_cast<int>(rows[row0].index0);
2114 	int i;
2115 	start += dir;
2116 	int stop = dir == -1 ? -1 : static_cast<int>(info.size());
2117 	codepageStr srch(search);
2118 	for (i = start; i != stop; i += dir) {
2119 		int shnum = info[i].shapenum;
2120 		const char *nm = studio->get_shape_name(shnum);
2121 		if (nm && search_name(nm, srch))
2122 			break;      // Found it.
2123 		const Shape_info &info = shapes_file->get_info(shnum);
2124 		if (info.has_frame_name_info()) {
2125 			bool found = false;
2126 			const std::vector<Frame_name_info> &nminf = info.get_frame_name_info();
2127 			for (const auto &it : nminf) {
2128 				int type = it.get_type();
2129 				int msgid = it.get_msgid();
2130 				if (type == -255 || type == -1 || msgid >= get_num_misc_names()
2131 				        || !get_misc_name(msgid))
2132 					continue;   // Keep looking.
2133 				if (search_name(get_misc_name(msgid), srch)) {
2134 					found = true;
2135 					break;      // Found it.
2136 				}
2137 			}
2138 			if (found)
2139 				break;
2140 		}
2141 	}
2142 	if (i == stop)
2143 		return;         // Not found.
2144 	goto_index(i);
2145 	select(i);
2146 	render();
2147 }
2148 
2149 /*
2150  *  Locate shape on game map.
2151  */
2152 
locate(bool upwards)2153 void Shape_chooser::locate(
2154     bool upwards
2155 ) {
2156 	if (selected < 0)
2157 		return;         // Shouldn't happen.
2158 	unsigned char data[Exult_server::maxlength];
2159 	unsigned char *ptr = &data[0];
2160 	int qual = ExultStudio::get_num_entry(get_loc_q(), -1);
2161 	int frnum = ExultStudio::get_num_entry(get_loc_f(), -1);
2162 	Write2(ptr, info[selected].shapenum);
2163 	Write2(ptr, frnum < 0 ? c_any_framenum : frnum);
2164 	Write2(ptr, qual < 0 ? c_any_qual : qual);
2165 	*ptr++ = upwards ? 1 : 0;
2166 	ExultStudio *studio = ExultStudio::get_instance();
2167 	studio->send_to_server(
2168 	    Exult_server::locate_shape, data, ptr - data);
2169 }
2170 
2171 /*
2172  *  Set up popup menu for shape browser.
2173  */
2174 
create_popup()2175 GtkWidget *Shape_chooser::create_popup(
2176 ) {
2177 	ExultStudio *studio = ExultStudio::get_instance();
2178 	create_popup_internal(true); // Create popup with groups, files.
2179 	if (selected >= 0) {    // Add editing choices.
2180 		Add_menu_item(popup, "Info...",
2181 		              G_CALLBACK(on_shapes_popup_info_activate), this);
2182 		if (studio->get_image_editor()) {
2183 			Add_menu_item(popup, "Edit...",
2184 			              G_CALLBACK(on_shapes_popup_edit_activate),
2185 			              this);
2186 			if (IS_FLAT(info[selected].shapenum) &&
2187 			        file_info == studio->get_vgafile())
2188 				Add_menu_item(popup, "Edit tiled...",
2189 				              G_CALLBACK(
2190 				                  on_shapes_popup_edtiles_activate), this);
2191 		}
2192 		// Separator.
2193 		Add_menu_item(popup);
2194 		// Add/del.
2195 		Add_menu_item(popup, "New frame",
2196 		              G_CALLBACK(on_shapes_popup_new_frame), this);
2197 		// Export/import.
2198 		Add_menu_item(popup, "Export frame...",
2199 		              G_CALLBACK(on_shapes_popup_export), this);
2200 		Add_menu_item(popup, "Import frame...",
2201 		              G_CALLBACK(on_shapes_popup_import), this);
2202 		if (!IS_FLAT(info[selected].shapenum) ||
2203 		        file_info != studio->get_vgafile()) {
2204 			// Separator.
2205 			Add_menu_item(popup);
2206 			// Export/import all frames.
2207 			Add_menu_item(popup, "Export all frames...",
2208 			              G_CALLBACK(on_shapes_popup_export_all), this);
2209 			Add_menu_item(popup, "Import all frames...",
2210 			              G_CALLBACK(on_shapes_popup_import_all), this);
2211 		}
2212 	}
2213 	if (ifile->is_flex()) {     // Multiple-shapes file (.vga)?
2214 		if (selected >= 0 && (!IS_FLAT(info[selected].shapenum) ||
2215 		                      file_info != studio->get_vgafile())) {
2216 			// Separator.
2217 			Add_menu_item(popup);
2218 			// Export/import shape.
2219 			Add_menu_item(popup, "Export shape...",
2220 			              G_CALLBACK(on_shapes_popup_export_shape), this);
2221 			Add_menu_item(popup, "Import shape...",
2222 			              G_CALLBACK(on_shapes_popup_import_shape), this);
2223 		}
2224 		// Separator.
2225 		Add_menu_item(popup);
2226 		Add_menu_item(popup, "New shape",
2227 		              G_CALLBACK(on_shapes_popup_new_shape), this);
2228 	}
2229 	return popup;
2230 }
2231 
2232 /*
2233  *  Create the list.
2234  */
2235 
Shape_chooser(Vga_file * i,unsigned char * palbuf,int w,int h,Shape_group * g,Shape_file_info * fi)2236 Shape_chooser::Shape_chooser(
2237     Vga_file *i,            // Where they're kept.
2238     unsigned char *palbuf,      // Palette, 3*256 bytes (rgb triples).
2239     int w, int h,           // Dimensions.
2240     Shape_group *g,
2241     Shape_file_info *fi
2242 ) : Object_browser(g, fi),
2243 	Shape_draw(i, palbuf, gtk_drawing_area_new()),
2244 	shapes_file(nullptr), framenum0(0),
2245 	info(0), rows(0), row0(0),
2246 	row0_voffset(0), total_height(0),
2247 	frames_mode(false), hoffset(0), voffset(0), status_id(-1), sel_changed(nullptr) {
2248 	rows.reserve(40);
2249 
2250 	// Put things in a vert. box.
2251 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2252 	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
2253 	set_widget(vbox); // This is our "widget"
2254 	gtk_widget_show(vbox);
2255 
2256 	GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2257 	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
2258 	gtk_widget_show(hbox);
2259 	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
2260 
2261 	// A frame looks nice.
2262 	GtkWidget *frame = gtk_frame_new(nullptr);
2263 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
2264 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2265 	gtk_widget_show(frame);
2266 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
2267 
2268 	// NOTE:  draw is in Shape_draw.
2269 	// Indicate the events we want.
2270 	gtk_widget_set_events(draw, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
2271 	                      | GDK_BUTTON_RELEASE_MASK
2272 	                      | GDK_BUTTON1_MOTION_MASK | GDK_KEY_PRESS_MASK);
2273 	// Set "configure" handler.
2274 	g_signal_connect(G_OBJECT(draw), "configure-event",
2275 	                 G_CALLBACK(Configure_chooser), this);
2276 	// Set "expose-event" - "draw" handler.
2277 	g_signal_connect(G_OBJECT(draw), "draw",
2278 	                 G_CALLBACK(expose), this);
2279 	// Keystroke.
2280 	g_signal_connect(G_OBJECT(draw), "key-press-event",
2281 	                 G_CALLBACK(on_draw_key_press),
2282 	                 this);
2283 	gtk_widget_set_can_focus(GTK_WIDGET(draw), TRUE);
2284 	// Set mouse click handler.
2285 	g_signal_connect(G_OBJECT(draw), "button-press-event",
2286 	                 G_CALLBACK(Mouse_press), this);
2287 	g_signal_connect(G_OBJECT(draw), "button-release-event",
2288 	                 G_CALLBACK(Mouse_release), this);
2289 	// Mouse motion.
2290 	g_signal_connect(G_OBJECT(draw), "drag-begin",
2291 	                 G_CALLBACK(drag_begin), this);
2292 	g_signal_connect(G_OBJECT(draw), "motion-notify-event",
2293 	                 G_CALLBACK(drag_motion), this);
2294 	g_signal_connect(G_OBJECT(draw), "drag-data-get",
2295 	                 G_CALLBACK(drag_data_get), this);
2296 	gtk_container_add(GTK_CONTAINER(frame), draw);
2297 	widget_set_margins(draw, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2298 	gtk_widget_set_size_request(draw, w, h);
2299 	gtk_widget_show(draw);
2300 	// Want vert. scrollbar for the shapes.
2301 	GtkAdjustment *shape_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
2302 	                           std::ceil(get_count() / 4.0), 1, 1, 1));
2303 	vscroll = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(shape_adj));
2304 	gtk_box_pack_start(GTK_BOX(hbox), vscroll, FALSE, TRUE, 0);
2305 	// Set scrollbar handler.
2306 	g_signal_connect(G_OBJECT(shape_adj), "value-changed",
2307 	                 G_CALLBACK(vscrolled), this);
2308 	gtk_widget_show(vscroll);
2309 	// Horizontal scrollbar.
2310 	shape_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 1600, 8, 16, 16));
2311 	hscroll = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(shape_adj));
2312 	gtk_box_pack_start(GTK_BOX(vbox), hscroll, FALSE, TRUE, 0);
2313 	// Set scrollbar handler.
2314 	g_signal_connect(G_OBJECT(shape_adj), "value-changed",
2315 	                 G_CALLBACK(hscrolled), this);
2316 //++++  gtk_widget_hide(hscroll);   // Only shown in 'frames' mode.
2317 
2318 	// At the bottom, status bar & frame:
2319 	GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
2320 	gtk_box_set_homogeneous(GTK_BOX(hbox1), FALSE);
2321 	gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
2322 	gtk_widget_show(hbox1);
2323 	// At left, a status bar.
2324 	sbar = gtk_statusbar_new();
2325 	sbar_sel = gtk_statusbar_get_context_id(GTK_STATUSBAR(sbar),
2326 	                                        "selection");
2327 	gtk_box_pack_start(GTK_BOX(hbox1), sbar, TRUE, TRUE, 0);
2328 	widget_set_margins(sbar, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2329 	gtk_widget_show(sbar);
2330 	GtkWidget *label = gtk_label_new("Frame:");
2331 	gtk_box_pack_start(GTK_BOX(hbox1), label, FALSE, FALSE, 0);
2332 	widget_set_margins(label, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2333 	gtk_widget_show(label);
2334 	// A spin button for frame#.
2335 	frame_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
2336 	                           16, 1,
2337 	                           0, 0.0));
2338 	fspin = gtk_spin_button_new(GTK_ADJUSTMENT(frame_adj),
2339 	                            1, 0);
2340 	g_signal_connect(G_OBJECT(frame_adj), "value-changed",
2341 	                 G_CALLBACK(frame_changed), this);
2342 	gtk_box_pack_start(GTK_BOX(hbox1), fspin, FALSE, FALSE, 0);
2343 	widget_set_margins(fspin, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2344 	gtk_widget_show(fspin);
2345 	// A toggle for 'All Frames'.
2346 	GtkWidget *allframes = gtk_toggle_button_new_with_label("Frames");
2347 	gtk_box_pack_start(GTK_BOX(hbox1), allframes, FALSE, FALSE, 0);
2348 	widget_set_margins(allframes, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
2349 	gtk_widget_show(allframes);
2350 	g_signal_connect(G_OBJECT(allframes), "toggled",
2351 	                 G_CALLBACK(all_frames_toggled), this);
2352 	// Add search controls to bottom.
2353 	gtk_box_pack_start(GTK_BOX(vbox),
2354 	                   create_controls(find_controls | locate_controls |
2355 	                                   locate_quality | locate_frame),
2356 	                   FALSE, FALSE, 0);
2357 }
2358 
2359 /*
2360  *  Delete.
2361  */
2362 
~Shape_chooser()2363 Shape_chooser::~Shape_chooser(
2364 ) {
2365 	gtk_widget_destroy(get_widget());
2366 }
2367 
2368 /*
2369  *  Unselect.
2370  */
2371 
unselect(bool need_render)2372 void Shape_chooser::unselect(
2373     bool need_render            // 1 to render and show.
2374 ) {
2375 	if (selected >= 0) {
2376 		selected = -1;
2377 		// Update spin button for frame #.
2378 		gtk_adjustment_set_value(frame_adj, 0);
2379 		gtk_widget_set_sensitive(fspin, false);
2380 		if (need_render) {
2381 			render();
2382 		}
2383 		if (sel_changed)    // Tell client.
2384 			(*sel_changed)();
2385 	}
2386 	update_statusbar();
2387 }
2388 
2389 /*
2390  *  Show selection or range in window.
2391  */
2392 
update_statusbar()2393 void Shape_chooser::update_statusbar(
2394 ) {
2395 	char buf[150];
2396 	if (status_id >= 0)     // Remove prev. selection msg.
2397 		gtk_statusbar_remove(GTK_STATUSBAR(sbar), sbar_sel, status_id);
2398 	if (selected >= 0) {
2399 		int shapenum = info[selected].shapenum;
2400 		int nframes = ifile->get_num_frames(shapenum);
2401 		g_snprintf(buf, sizeof(buf), "Shape %d (0x%03x, %d frames)",
2402 		           shapenum, shapenum, nframes);
2403 		ExultStudio *studio = ExultStudio::get_instance();
2404 		if (shapes_file) {
2405 			const char *nm;
2406 			if ((nm = studio->get_shape_name(shapenum))) {
2407 				int len = strlen(buf);
2408 				g_snprintf(buf + len, sizeof(buf) - len,
2409 				           ":  '%s'", nm);
2410 			}
2411 			if (shapes_file->read_info(studio->get_game_type(), true))
2412 				studio->set_shapeinfo_modified();
2413 			int frnum = info[selected].framenum;
2414 			const Shape_info &inf = shapes_file->get_info(shapenum);
2415 			const Frame_name_info *nminf;
2416 			if (inf.has_frame_name_info() &&
2417 			        (nminf = inf.get_frame_name(frnum, -1)) != nullptr) {
2418 				int type = nminf->get_type();
2419 				int msgid = nminf->get_msgid();
2420 				if (type >= 0 && msgid < get_num_misc_names()) {
2421 					const char *msgstr = get_misc_name(msgid);
2422 					int len = strlen(buf);
2423 					// For safety.
2424 					if (!nm) nm = "";
2425 					if (!msgstr) msgstr = "";
2426 					if (type > 0) {
2427 						const char *otmsgstr;
2428 						if (type > 2)
2429 							otmsgstr = "<NPC Name>";
2430 						else {
2431 							int otmsg = nminf->get_othermsg();
2432 							otmsgstr = otmsg == -255 ? nm :
2433 							           (otmsg == -1 || otmsg >= get_num_misc_names() ? "" :
2434 							            get_misc_name(otmsg));
2435 							if (!otmsgstr) otmsgstr = "";
2436 						}
2437 						const char *prefix = nullptr;
2438 						const char *suffix = nullptr;
2439 						if (type & 1) {
2440 							prefix = otmsgstr;
2441 							suffix = msgstr;
2442 						} else {
2443 							prefix = msgstr;
2444 							suffix = otmsgstr;
2445 						}
2446 						g_snprintf(buf + len, sizeof(buf) - len,
2447 						           "  -  '%s%s'", prefix, suffix);
2448 					} else
2449 						g_snprintf(buf + len, sizeof(buf) - len,
2450 						           "  -  '%s'", msgstr);
2451 				}
2452 			}
2453 		}
2454 		status_id = gtk_statusbar_push(GTK_STATUSBAR(sbar),
2455 		                               sbar_sel, buf);
2456 	} else if (!info.empty() && !group) {
2457 		int first_shape = info[rows[row0].index0].shapenum;
2458 		g_snprintf(buf, sizeof(buf), "Shapes %d to %d (0x%03x to 0x%03x)",
2459 		           first_shape, last_shape, first_shape, last_shape);
2460 		status_id = gtk_statusbar_push(GTK_STATUSBAR(sbar),
2461 		                               sbar_sel, buf);
2462 	} else
2463 		status_id = -1;
2464 }
2465 
2466 
2467