1 /**
2  ** Combo.cc - A combination of multiple objects.
3  **
4  ** Written: 4/26/02 - JSF
5  **/
6 
7 /*
8 Copyright (C) 2002-2020 The Exult Team
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 "combo.h"
30 
31 #include "exceptions.h"
32 #include "exult_constants.h"
33 #include "ibuf8.h"
34 #include "objserial.h"
35 #include "shapefile.h"
36 #include "shapegroup.h"
37 #include "shapevga.h"
38 #include "u7drag.h"
39 
40 using std::cout;
41 using std::endl;
42 using std::make_unique;
43 using std::unique_ptr;
44 
45 class Game_object;
46 
47 const int border = 2;           // Border at bottom, sides of each
48 //   combo in browser.
49 const int maxtiles = 32;        // Max. width/height in tiles.
50 
51 /*
52  *  Open combo window (if not already open).
53  */
54 
on_new_combo1_activate(GtkMenuItem * menuitem,gpointer user_data)55 C_EXPORT void on_new_combo1_activate(
56     GtkMenuItem     *menuitem,
57     gpointer         user_data
58 ) {
59 	ignore_unused_variable_warning(menuitem, user_data);
60 	ExultStudio *studio = ExultStudio::get_instance();
61 	studio->open_combo_window();
62 }
open_combo_window()63 void ExultStudio::open_combo_window(
64 ) {
65 	if (combowin && combowin->is_visible())
66 		return;         // Already open.
67 	if (!vgafile) {
68 		EStudio::Alert("'shapes.vga' file isn't present");
69 		return;
70 	}
71 	auto *svga = static_cast<Shapes_vga_file *>(vgafile->get_ifile());
72 	delete combowin;        // Delete old (svga may have changed).
73 	combowin = new Combo_editor(svga, palbuf.get());
74 	combowin->show(true);
75 	// Set edit-mode to pick.
76 	GtkWidget *mitem = get_widget("pick_for_combo1");
77 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mitem), TRUE);
78 }
79 
80 /*
81  *  Close the combo window.
82  */
83 
close_combo_window()84 void ExultStudio::close_combo_window(
85 ) {
86 	if (combowin)
87 		combowin->show(false);
88 }
89 
90 /*
91  *  Save combos.
92  */
93 
save_combos()94 void ExultStudio::save_combos(
95 ) {
96 	// Get file info.
97 	Shape_file_info *combos = files->create("combos.flx");
98 	try {
99 		if (combos)
100 			combos->flush();
101 	} catch (const exult_exception &e) {
102 		EStudio::Alert("%s", e.what());
103 	}
104 }
105 
106 /*
107  *  Callbacks for "Combo" editor window.
108  */
on_combo_draw_expose_event(GtkWidget * widget,cairo_t * cairo,gpointer data)109 gboolean Combo_editor::on_combo_draw_expose_event(
110     GtkWidget *widget,      // The view window.
111     cairo_t *cairo,
112     gpointer data) {
113 	ignore_unused_variable_warning(data);
114 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
115 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(widget))), "user_data"));
116 	GdkRectangle area = { 0, 0, 0, 0 };
117 	gdk_cairo_get_clip_rectangle(cairo, &area);
118 	combo->set_graphic_context(cairo);
119 	combo->render_area(&area);
120 	combo->set_graphic_context(nullptr);
121 	return TRUE;
122 }
123 
124 C_EXPORT void
on_combo_remove_clicked(GtkButton * button,gpointer user_data)125 on_combo_remove_clicked(GtkButton       *button,
126                         gpointer         user_data) {
127 	ignore_unused_variable_warning(user_data);
128 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
129 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
130 	combo->remove();
131 }
132 
133 C_EXPORT void
on_combo_apply_clicked(GtkButton * button,gpointer user_data)134 on_combo_apply_clicked(GtkButton       *button,
135                        gpointer         user_data) {
136 	ignore_unused_variable_warning(user_data);
137 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
138 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
139 	combo->save();
140 }
141 
142 C_EXPORT void
on_combo_ok_clicked(GtkButton * button,gpointer user_data)143 on_combo_ok_clicked(GtkButton       *button,
144                     gpointer         user_data) {
145 	ignore_unused_variable_warning(user_data);
146 	GtkWidget *win = gtk_widget_get_toplevel(GTK_WIDGET(button));
147 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
148 	                  G_OBJECT(win), "user_data"));
149 	combo->save();
150 	gtk_widget_hide(win);
151 }
152 
153 C_EXPORT void
on_combo_locx_changed(GtkSpinButton * button,gpointer user_data)154 on_combo_locx_changed(GtkSpinButton *button,
155                       gpointer     user_data) {
156 	ignore_unused_variable_warning(user_data);
157 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
158 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
159 	combo->set_position();
160 }
161 
162 C_EXPORT void
on_combo_locy_changed(GtkSpinButton * button,gpointer user_data)163 on_combo_locy_changed(GtkSpinButton *button,
164                       gpointer     user_data) {
165 	ignore_unused_variable_warning(user_data);
166 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
167 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
168 	combo->set_position();
169 }
170 
171 C_EXPORT void
on_combo_locz_changed(GtkSpinButton * button,gpointer user_data)172 on_combo_locz_changed(GtkSpinButton *button,
173                       gpointer     user_data) {
174 	ignore_unused_variable_warning(user_data);
175 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
176 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
177 	combo->set_position();
178 }
179 
180 C_EXPORT void
on_combo_order_changed(GtkSpinButton * button,gpointer user_data)181 on_combo_order_changed(GtkSpinButton *button,
182                        gpointer     user_data) {
183 	ignore_unused_variable_warning(user_data);
184 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
185 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(button))), "user_data"));
186 	combo->set_order();
187 }
188 
189 /*
190  *  Mouse events in draw area.
191  */
on_combo_draw_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer data)192 C_EXPORT gboolean on_combo_draw_button_press_event(
193     GtkWidget *widget,      // The view window.
194     GdkEventButton *event,
195     gpointer data           // ->Combo_chooser.
196 ) {
197 	ignore_unused_variable_warning(data);
198 	auto *combo = static_cast<Combo_editor *>(g_object_get_data(
199 	                  G_OBJECT(gtk_widget_get_toplevel(GTK_WIDGET(widget))), "user_data"));
200 	return combo->mouse_press(event);
201 }
202 
203 /*
204  *  Which member makes the better 'hot-spot' in a combo, where 'better'
205  *  means lower, then southmost, then eastmost.
206  *
207  *  Output: 0 if c0, 1 if c1
208  */
209 
hot_spot_compare(Combo_member & c0,Combo_member & c1)210 int hot_spot_compare(
211     Combo_member &c0,
212     Combo_member &c1
213 ) {
214 	if (c0.tz < c1.tz)
215 		return 0;
216 	else if (c1.tz < c0.tz)
217 		return 1;
218 	else if (c0.ty > c1.ty)
219 		return 0;
220 	else if (c1.ty > c0.ty)
221 		return 1;
222 	else
223 		return c0.tx >= c1.tx ? 0 : 1;
224 }
225 
226 /*
227  *  Get footprint of given member.
228  */
229 
get_member_footprint(int i)230 TileRect Combo::get_member_footprint(
231     int i               // i'th member.
232 ) {
233 	Combo_member *m = members[i];
234 	// Get tile dims.
235 	const Shape_info &info = shapes_file->get_info(m->shapenum);
236 	int xtiles = info.get_3d_xtiles(m->framenum);
237 	int ytiles = info.get_3d_ytiles(m->framenum);
238 	// Get tile footprint.
239 	TileRect box(m->tx - xtiles + 1, m->ty - ytiles + 1,
240 	             xtiles, ytiles);
241 	return box;
242 }
243 
244 /*
245  *  Create empty combo.
246  */
247 
Combo(Shapes_vga_file * svga)248 Combo::Combo(
249     Shapes_vga_file *svga
250 ) : shapes_file(svga), hot_index(-1),
251 	starttx(c_num_tiles), startty(c_num_tiles),
252 	tilefoot(0, 0, 0, 0) {
253 	// Read info. the first time.
254 	ExultStudio *es = ExultStudio::get_instance();
255 	if (shapes_file->read_info(es->get_game_type(), true))
256 		es->set_shapeinfo_modified();
257 }
258 
259 /*
260  *  Copy another.
261  */
262 
Combo(const Combo & c2)263 Combo::Combo(
264     const Combo &c2
265 ) : shapes_file(c2.shapes_file), hot_index(c2.hot_index), starttx(c2.starttx),
266 	startty(c2.startty), name(c2.name), tilefoot(c2.tilefoot) {
267 	for (auto *m : c2.members) {
268 		members.push_back(new Combo_member(m->tx, m->ty, m->tz,
269 		                                   m->shapenum, m->framenum));
270 	}
271 }
272 
273 /*
274  *  Clean up.
275  */
276 
~Combo()277 Combo::~Combo(
278 ) {
279 	for (auto *member : members)
280 		delete member;
281 }
282 
283 /*
284  *  Add a new object.
285  */
286 
add(int tx,int ty,int tz,int shnum,int frnum,bool toggle)287 void Combo::add(
288     int tx, int ty, int tz,     // Location rel. to top-left.
289     int shnum, int frnum,       // Shape.
290     bool toggle
291 ) {
292 	// Look for identical shape, pos.
293 	for (auto it = members.begin();
294 	        it != members.end(); ++it) {
295 		Combo_member *m = *it;
296 		if (tx == m->tx && ty == m->ty && tz == m->tz &&
297 		        shnum == m->shapenum && frnum == m->framenum) {
298 			if (toggle)
299 				remove(it - members.begin());
300 			return;     // Don't add same one twice.
301 		}
302 	}
303 	// Get tile dims.
304 	const Shape_info &info = shapes_file->get_info(shnum);
305 	int xtiles = info.get_3d_xtiles(frnum);
306 	int ytiles = info.get_3d_ytiles(frnum);
307 	int ztiles = info.get_3d_height();
308 	// Get tile footprint.
309 	TileRect box(tx - xtiles + 1, ty - ytiles + 1, xtiles, ytiles);
310 	if (members.empty())        // First one?
311 		tilefoot = box;     // Init. total footprint.
312 	else {
313 		// Too far away?
314 		if (tilefoot.x + tilefoot.w - box.x > maxtiles ||
315 		        box.x + box.w - tilefoot.x > maxtiles ||
316 		        tilefoot.y + tilefoot.h - box.y > maxtiles ||
317 		        box.y + box.h - tilefoot.y > maxtiles) {
318 			EStudio::Alert(
319 			    "New object is too far (> 32) from others");
320 			return;
321 		}
322 		// Add to footprint.
323 		tilefoot = tilefoot.add(box);
324 	}
325 	auto *memb = new Combo_member(tx, ty, tz, shnum, frnum);
326 	members.push_back(memb);
327 	// Figure visible top-left tile, with
328 	//   1 to spare.
329 	int vtx = tx - xtiles - 2 - (tz + ztiles + 1) / 2;
330 	int vty = ty - ytiles - 2 - (tz + ztiles + 1) / 2;
331 	if (vtx < starttx)      // Adjust our starting point.
332 		starttx = vtx;
333 	if (vty < startty)
334 		startty = vty;
335 	if (hot_index == -1 ||      // First one, or better than prev?
336 	        hot_spot_compare(*memb, *members[hot_index]) == 0)
337 		hot_index = members.size() - 1;
338 }
339 
340 /*
341  *  Remove i'th object.
342  */
343 
remove(int i)344 void Combo::remove(
345     int i
346 ) {
347 	if (i < 0 || unsigned(i) >= members.size())
348 		return;
349 	// Get and remove i'th entry.
350 	auto it = members.begin() + i;
351 	Combo_member *m = *it;
352 	members.erase(it);
353 	delete m;
354 	hot_index = -1;         // Figure new hot-spot, footprint.
355 	tilefoot = TileRect(0, 0, 0, 0);
356 	for (auto it = members.begin();
357 	        it != members.end(); ++it) {
358 		Combo_member *m = *it;
359 		int index = it - members.begin();
360 		TileRect box = get_member_footprint(index);
361 		if (hot_index == -1) {  // First?
362 			hot_index = 0;
363 			tilefoot = box;
364 		} else {
365 			if (hot_spot_compare(*m, *members[hot_index]) == 0)
366 				hot_index = index;
367 			tilefoot = tilefoot.add(box);
368 		}
369 	}
370 }
371 
372 /*
373  *  Paint in a drawing area.
374  */
375 
draw(Shape_draw * draw,int selected,int xoff,int yoff)376 void Combo::draw(
377     Shape_draw *draw,
378     int selected,           // Index of 'selected' item, or -1.
379     int xoff, int yoff      // Offset within draw.
380 ) {
381 	int selx = -1000;
382 	int sely = -1000;
383 	bool selfound = false;
384 	for (auto it = members.begin();
385 	        it != members.end(); ++it) {
386 		Combo_member *m = *it;
387 		// Figure pixels up-left for lift.
388 		int lft = m->tz * (c_tilesize / 2);
389 		// Figure relative tile.
390 		int mtx = m->tx - starttx;
391 		int mty = m->ty - startty;
392 		// Hot spot:
393 		int x = mtx * c_tilesize - lft;
394 		int y = mty * c_tilesize - lft;
395 		Shape_frame *shape = shapes_file->get_shape(m->shapenum,
396 		                     m->framenum);
397 		if (!shape)
398 			continue;
399 		// But draw_shape uses top-left.
400 		x -= shape->get_xleft();
401 		y -= shape->get_yabove();
402 		x += xoff;
403 		y += yoff;   // Add offset within area.
404 		draw->draw_shape(shape, x, y);
405 		if (it - members.begin() == selected) {
406 			selx = x;   // Save coords for selected.
407 			sely = y;
408 			selfound = true;
409 		}
410 	}
411 	if (selfound) {         // Now put border around selected.
412 		Combo_member *m = members[selected];
413 		// FOR NOW, use color #1 ++++++++
414 		draw->draw_shape_outline(m->shapenum, m->framenum,
415 		                         selx, sely, 1);
416 	}
417 }
418 
419 /*
420  *  Find last member in list that contains a mouse point.
421  *
422  *  Output: Index of member found, or -1 if none.
423  */
424 
find(int mx,int my)425 int Combo::find(
426     int mx, int my          // Mouse position in draw area.
427 ) {
428 	int cnt = members.size();
429 	for (int i = cnt - 1; i >= 0; i--) {
430 		Combo_member *m = members[i];
431 		// Figure pixels up-left for lift.
432 		int lft = m->tz * (c_tilesize / 2);
433 		// Figure relative tile.
434 		int mtx = m->tx - starttx;
435 		int mty = m->ty - startty;
436 		int x = mtx * c_tilesize - lft;
437 		int y = mty * c_tilesize - lft;
438 		Shape_frame *frame = shapes_file->get_shape(
439 		                         m->shapenum, m->framenum);
440 		if (frame && frame->has_point(mx - x, my - y))
441 			return i;
442 	}
443 	return -1;
444 }
445 
446 /*
447  *  Write out.
448  *
449  *  Output: Allocated buffer containing result.
450  */
451 
write(int & datalen)452 unique_ptr<unsigned char[]> Combo::write(
453     int &datalen            // Actual length of data in buf. is
454     //   returned here.
455 ) {
456 	int namelen = name.length();    // Name length.
457 	// Room for our data + members.
458 	auto buf = make_unique<unsigned char[]>(namelen + 1 +
459 	                                        7 * 4 + members.size() * (5 * 4));
460 	unsigned char *ptr = buf.get();
461 	Serial_out out(ptr);
462 	out << name;
463 	out << hot_index << starttx << startty;
464 	out << static_cast<short>(members.size());  // # members to follow.
465 	for (auto *m : members) {
466 		out << m->tx << m->ty << m->tz << m->shapenum <<
467 		    m->framenum;
468 	}
469 	datalen = ptr - buf.get();        // Return actual length.
470 	return buf;
471 }
472 
473 /*
474  *  Read in.
475  *
476  *  Output: ->past actual data read.
477  */
478 
read(const unsigned char * buf,int bufsize)479 const unsigned char *Combo::read(
480     const unsigned char *buf,
481     int bufsize
482 ) {
483 	ignore_unused_variable_warning(bufsize);
484 	const unsigned char *ptr = buf;
485 	Serial_in in(ptr);
486 	in << name;
487 	in << hot_index << starttx << startty;
488 	short cnt;
489 	in << cnt;          // # members to follow.
490 	for (int i = 0; i < cnt; i++) {
491 		short tx;
492 		short ty;
493 		short tz;
494 		short shapenum;
495 		short framenum;
496 		in << tx << ty << tz << shapenum << framenum;
497 		auto *memb = new Combo_member(tx, ty, tz,
498 		                              shapenum, framenum);
499 		members.push_back(memb);
500 		TileRect box = get_member_footprint(i);
501 		if (i == 0)     // Figure footprint.
502 			tilefoot = box;
503 		else
504 			tilefoot = tilefoot.add(box);
505 	}
506 	return buf;
507 }
508 
509 /*
510  *  Set to edit an existing combo.
511  */
512 
set_combo(Combo * newcombo,int findex)513 void Combo_editor::set_combo(
514     Combo *newcombo,        // We'll own this.
515     int findex          // File index.
516 ) {
517 	delete combo;
518 	combo = newcombo;
519 	file_index = findex;
520 	selected = -1;
521 	ExultStudio::get_instance()->set_entry(
522 	    "combo_name", combo->name.c_str(), true);
523 	set_controls();         // No selection now.
524 	render();
525 }
526 
527 /*
528  *  Create combo editor.
529  */
530 
Combo_editor(Shapes_vga_file * svga,unsigned char * palbuf)531 Combo_editor::Combo_editor(
532     Shapes_vga_file *svga,      // File containing shapes.
533     unsigned char *palbuf       // Palette for drawing shapes.
534 ) : Shape_draw(svga, palbuf,
535 	               ExultStudio::get_instance()->get_widget("combo_draw")),
536 	selected(-1), setting_controls(false), file_index(-1) {
537 	static bool first = true;
538 	combo = new Combo(svga);
539 	win = ExultStudio::get_instance()->get_widget("combo_win");
540 	g_object_set_data(G_OBJECT(win), "user_data", this);
541 	g_signal_connect(G_OBJECT(draw), "draw",
542 	                 G_CALLBACK(on_combo_draw_expose_event), this);
543 	if (first) {        // Indicate the events we want.
544 		gtk_widget_set_events(draw, GDK_EXPOSURE_MASK
545 		                      | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
546 		                      | GDK_BUTTON1_MOTION_MASK);
547 		first = false;
548 	}
549 	set_controls();
550 }
551 
552 /*
553  *  Clean up.
554  */
555 
~Combo_editor()556 Combo_editor::~Combo_editor(
557 ) {
558 	delete combo;
559 }
560 
561 /*
562  *  Show/hide.
563  */
564 
show(bool tf)565 void Combo_editor::show(
566     bool tf
567 ) {
568 	if (tf)
569 		gtk_widget_show(win);
570 	else
571 		gtk_widget_hide(win);
572 }
573 
574 /*
575  *  Display.
576  */
577 
render_area(GdkRectangle * area)578 void Combo_editor::render_area(
579     GdkRectangle *area      // 0 for whole draw area, as Combo_editor::render().
580 ) {
581 	if (!draw)
582 		return;
583 	if (!area || !drawgc) {
584 		gtk_widget_queue_draw(draw);
585 		return;
586 	}
587 	Shape_draw::configure();    // Setup the first time.
588 	// Get dims.
589 	cairo_rectangle(drawgc, area->x, area->y, area->width, area->height);
590 	cairo_clip(drawgc);
591 	iwin->fill8(255);       // Fill with background color.
592 	combo->draw(this, selected);    // Draw shapes.
593 	Shape_draw::show(area->x, area->y, area->width, area->height);
594 }
595 
596 /*
597  *  Set controls according to what's selected.
598  */
599 
set_controls()600 void Combo_editor::set_controls(
601 ) {
602 	setting_controls = true;    // Avoid updating.
603 	ExultStudio *studio = ExultStudio::get_instance();
604 	Combo_member *m = combo->get(selected);
605 	if (!m) {           // None selected?
606 		studio->set_spin("combo_locx", 0, false);
607 		studio->set_spin("combo_locy", 0, false);
608 		studio->set_spin("combo_locz", 0, false);
609 		studio->set_spin("combo_order", 0, false);
610 		studio->set_sensitive("combo_remove", false);
611 	} else {
612 		GtkAllocation alloc = {0, 0, 0, 0};
613 		gtk_widget_get_allocation(draw, &alloc);
614 		int draww = alloc.width;
615 		int drawh = alloc.height;
616 		studio->set_sensitive("combo_locx", true);
617 		studio->set_spin("combo_locx", m->tx - combo->starttx,
618 		                 0, draww / c_tilesize);
619 		studio->set_sensitive("combo_locy", true);
620 		studio->set_spin("combo_locy", m->ty - combo->startty,
621 		                 0, drawh / c_tilesize);
622 		studio->set_sensitive("combo_locz", true);
623 		studio->set_spin("combo_locz", m->tz, 0, 15);
624 		studio->set_sensitive("combo_order", true);
625 		studio->set_spin("combo_order", selected, 0,
626 		                 combo->members.size() - 1);
627 		studio->set_sensitive("combo_remove", true);
628 	}
629 	setting_controls = false;
630 }
631 
632 /*
633  *  Handle a mouse-press event.
634  */
635 
mouse_press(GdkEventButton * event)636 gint Combo_editor::mouse_press(
637     GdkEventButton *event
638 ) {
639 	if (event->button != 1)
640 		return FALSE;       // Handling left-click.
641 	// Get mouse position, draw dims.
642 	int mx = static_cast<int>(event->x);
643 	int my = static_cast<int>(event->y);
644 	selected = combo->find(mx, my); // Find it (or -1 if not found).
645 	set_controls();
646 	render();
647 	return TRUE;
648 }
649 
650 /*
651  *  Move the selected item within the order.
652  */
653 
set_order()654 void Combo_editor::set_order(
655 ) {
656 	if (setting_controls || selected < 0)
657 		return;
658 	ExultStudio *studio = ExultStudio::get_instance();
659 	int newi = studio->get_spin("combo_order");
660 	if (selected == newi)
661 		return;         // Already correct.
662 	int dir = newi > selected ? 1 : -1;
663 	while (newi != selected) {
664 		Combo_member *tmp = combo->members[selected + dir];
665 		combo->members[selected + dir] = combo->members[selected];
666 		combo->members[selected] = tmp;
667 		selected += dir;
668 	}
669 	render();
670 }
671 
672 /*
673  *  Move the selected item to the desired position in the spin buttons.
674  */
675 
set_position()676 void Combo_editor::set_position(
677 ) {
678 	Combo_member *m = combo->get(selected);
679 	if (!m || setting_controls)
680 		return;
681 	ExultStudio *studio = ExultStudio::get_instance();
682 	m->tx = combo->starttx + studio->get_spin("combo_locx");
683 	m->ty = combo->startty + studio->get_spin("combo_locy");
684 	m->tz = studio->get_spin("combo_locz");
685 	render();
686 }
687 
688 /*
689  *  Get # shapes we can display.
690  */
691 
get_count()692 int Combo_chooser::get_count(
693 ) {
694 	return group ? group->size() : combos.size();
695 }
696 
697 /*
698  *  Add an object/shape picked from Exult.
699  */
700 
add(unsigned char * data,int datalen,bool toggle)701 void Combo_editor::add(
702     unsigned char *data,        // Serialized object.
703     int datalen,
704     bool toggle
705 ) {
706 	Game_object *addr;
707 	int tx;
708 	int ty;
709 	int tz;
710 	int shape;
711 	int frame;
712 	int quality;
713 	std::string name;
714 	if (!Object_in(data, datalen, addr, tx, ty, tz, shape, frame,
715 	               quality, name)) {
716 		cout << "Error decoding object" << endl;
717 		return;
718 	}
719 	combo->add(tx, ty, tz, shape, frame, toggle);
720 	render();
721 }
722 
723 /*
724  *  Remove selected.
725  */
726 
remove()727 void Combo_editor::remove(
728 ) {
729 	if (selected >= 0) {
730 		combo->remove(selected);
731 		selected = -1;
732 		set_controls();
733 		render();
734 	}
735 }
736 
737 /*
738  *  Save combo.
739  */
740 
save()741 void Combo_editor::save(
742 ) {
743 	ExultStudio *studio = ExultStudio::get_instance();
744 	// Get name from field.
745 	combo->name = studio->get_text_entry("combo_name");
746 	auto *flex_info = dynamic_cast<Flex_file_info *>
747 	                  (studio->get_files()->create("combos.flx"));
748 	if (!flex_info) {
749 		EStudio::Alert("Can't open 'combos.flx'");
750 		return;
751 	}
752 	flex_info->set_modified();
753 	int len;            // Serialize.
754 	auto newbuf = combo->write(len);
755 	// Update or append file data.
756 	flex_info->set(file_index == -1 ? flex_info->size() : file_index, std::move(newbuf), len);
757 	auto *chooser = dynamic_cast<Combo_chooser *>(studio->get_browser());
758 	if (chooser)            // Browser open?
759 		file_index = chooser->add(new Combo(*combo), file_index);
760 }
761 
762 /*
763  *  Blit onto screen.
764  */
765 
show(int x,int y,int w,int h)766 void Combo_chooser::show(
767     int x, int y, int w, int h  // Area to blit.
768 ) {
769 	Shape_draw::show(x, y, w, h);
770 	if ((selected >= 0) && (drawgc != nullptr)) {    // Show selected.
771 		TileRect b = info[selected].box;
772 		// Draw yellow box.
773 		cairo_set_line_width(drawgc, 1.0);
774 		cairo_set_source_rgb(drawgc,
775 		                     ((drawfg >> 16) & 255) / 255.0,
776 		                     ((drawfg >> 8) & 255) / 255.0,
777 		                     (drawfg & 255) / 255.0);
778 		cairo_rectangle(drawgc, b.x, b.y, b.w, b.h);
779 		cairo_stroke(drawgc);
780 	}
781 }
782 
783 /*
784  *  Select an entry.  This should be called after rendering
785  *  the combo.
786  */
787 
select(int new_sel)788 void Combo_chooser::select(
789     int new_sel
790 ) {
791 	if (new_sel < 0 || new_sel >= info_cnt)
792 		return;         // Bad value.
793 	selected = new_sel;
794 	enable_controls();
795 	int num = info[selected].num;
796 	Combo *combo = combos[num];
797 	// Remove prev. selection msg.
798 	//gtk_statusbar_pop(GTK_STATUSBAR(sbar), sbar_sel);
799 	char buf[150];          // Show new selection.
800 	g_snprintf(buf, sizeof(buf), "Combo %d", num);
801 	if (combo && !combo->name.empty()) {
802 		int len = strlen(buf);
803 		g_snprintf(buf + len, sizeof(buf) - len,
804 		           ":  '%s'", combo->name.c_str());
805 	}
806 	gtk_statusbar_push(GTK_STATUSBAR(sbar), sbar_sel, buf);
807 }
808 
809 /*
810  *  Unselect.
811  */
812 
unselect(bool need_render)813 void Combo_chooser::unselect(
814     bool need_render            // 1 to render and show.
815 ) {
816 	if (selected >= 0) {
817 		selected = -1;
818 		if (need_render) {
819 			render();
820 		}
821 		if (sel_changed)    // Tell client.
822 			(*sel_changed)();
823 	}
824 	enable_controls();      // Enable/disable controls.
825 	if (info_cnt > 0) {
826 		char buf[150];          // Show new selection.
827 		g_snprintf(buf, sizeof(buf), "Combos %d to %d",
828 		           info[0].num, info[info_cnt - 1].num);
829 		gtk_statusbar_push(GTK_STATUSBAR(sbar), sbar_sel, buf);
830 	} else {
831 		gtk_statusbar_push(GTK_STATUSBAR(sbar), sbar_sel,
832 		                   "No combos");
833 	}
834 }
835 
836 /*
837  *  Load/reload from file.
838  */
839 
load_internal()840 void Combo_chooser::load_internal(
841 ) {
842 	unsigned cnt = combos.size();
843 	for (unsigned i = 0; i < cnt; i++)   // Delete all the combos.
844 		delete combos[i];
845 	unsigned num_combos = flex_info->size();
846 	// We need 'shapes.vga'.
847 	Shape_file_info *svga_info =
848 	    ExultStudio::get_instance()->get_vgafile();
849 	Shapes_vga_file *svga = svga_info ?
850 	                        static_cast<Shapes_vga_file *>(svga_info->get_ifile()) : nullptr;
851 	combos.resize(num_combos);  // Set size of list.
852 	if (!svga)
853 		num_combos = 0;
854 	// Read them all in.
855 	for (unsigned i = 0; i < num_combos; i++) {
856 		size_t len;
857 		unsigned char *buf = flex_info->get(i, len);
858 		auto *combo = new Combo(svga);
859 		combo->read(buf, len);
860 		combos[i] = combo;  // Store in list.
861 	}
862 }
863 
864 /*
865  *  Render as many combos as fit in the combo chooser window.
866  */
867 
render()868 void Combo_chooser::render(
869 ) {
870 	// Look for selected frame.
871 	int selcombo = -1;
872 	int new_selected = -1;
873 	if (selected >= 0)      // Save selection info.
874 		selcombo = info[selected].num;
875 	// Remove "selected" message.
876 	//gtk_statusbar_pop(GTK_STATUSBAR(sbar), sbar_sel);
877 	delete [] info;         // Delete old info. list.
878 	// Get drawing area dimensions.
879 	GtkAllocation alloc = {0, 0, 0, 0};
880 	gtk_widget_get_allocation(draw, &alloc);
881 	gint winw = alloc.width;
882 	gint winh = alloc.height;
883 	// Provide more than enough room.
884 	info = new Combo_info[256];
885 	info_cnt = 0;           // Count them.
886 	// Clear window first.
887 	iwin->fill8(255);       // Background color.
888 	int index = index0;
889 	// We'll always show 128x128.
890 	const int combow = 128;
891 	const int comboh = 128;
892 	int total_cnt = get_count();
893 	int y = border;
894 	// Show bottom if at least 1/2 vis.
895 	while (index < total_cnt && y + comboh / 2 + border <= winh) {
896 		int x = border;
897 		int cliph = y + comboh <= winh ? comboh : (winh - y);
898 		while (index < total_cnt && x + combow + border <= winw) {
899 			iwin->set_clip(x, y, combow, cliph);
900 			int combonum = group ? (*group)[index] : index;
901 			combos[combonum]->draw(this, -1, x, y);
902 			iwin->clear_clip();
903 			// Store info. about where drawn.
904 			info[info_cnt].set(combonum, x, y, combow, comboh);
905 			if (combonum == selcombo)
906 				// Found the selected combo.
907 				new_selected = info_cnt;
908 			info_cnt++;
909 			index++;        // Next combo.
910 			x += combow + border;
911 		}
912 		y += comboh + border;
913 	}
914 	if (new_selected == -1)
915 		unselect(false);
916 	else
917 		select(new_selected);
918 	gtk_widget_queue_draw(draw);
919 }
920 
921 /*
922  *  Scroll to a new combo.
923  */
924 
scroll(int newindex)925 void Combo_chooser::scroll(
926     int newindex            // Abs. index of leftmost to show.
927 ) {
928 	int total = combos.size();
929 	if (index0 < newindex)  // Going forwards?
930 		index0 = newindex < total ? newindex : total;
931 	else if (index0 > newindex) // Backwards?
932 		index0 = newindex >= 0 ? newindex : 0;
933 	render();
934 }
935 
936 /*
937  *  Scroll up/down by one row.
938  */
939 
scroll(bool upwards)940 void Combo_chooser::scroll(
941     bool upwards
942 ) {
943 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(vscroll));
944 	gdouble delta = gtk_adjustment_get_step_increment(adj);
945 	if (upwards)
946 		delta = -delta;
947 	gtk_adjustment_set_value(adj, delta + gtk_adjustment_get_value(adj));
948 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
949 	scroll(static_cast<gint>(gtk_adjustment_get_value(adj)));
950 }
951 
952 /*
953  *  Someone wants the dragged combo.
954  */
955 
drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * seldata,guint info,guint time,gpointer data)956 void Combo_chooser::drag_data_get(
957     GtkWidget *widget,      // The view window.
958     GdkDragContext *context,
959     GtkSelectionData *seldata,  // Fill this in.
960     guint info,
961     guint time,
962     gpointer data           // ->Shape_chooser.
963 ) {
964 	ignore_unused_variable_warning(widget, context, time);
965 	cout << "In DRAG_DATA_GET of Combo for '"
966 	     << gdk_atom_name(gtk_selection_data_get_target(seldata))
967 	     << "'" << endl;
968 	auto *chooser = static_cast<Combo_chooser *>(data);
969 	if (chooser->selected < 0 || info != U7_TARGET_COMBOID)
970 		return;         // Not sure about this.
971 	// Get combo #.
972 	int num = chooser->info[chooser->selected].num;
973 	Combo *combo = chooser->combos[num];
974 	// Get enough memory.
975 	int cnt = combo->members.size();
976 	int buflen = U7DND_DATA_LENGTH(5 + cnt * 5);
977 	cout << "Buflen = " << buflen << endl;
978 	auto *buf = new unsigned char[buflen];
979 	auto *ents = new U7_combo_data[cnt];
980 	// Get 'hot-spot' member.
981 	Combo_member *hot = combo->members[combo->hot_index];
982 	for (int i = 0; i < cnt; i++) {
983 		Combo_member *m = combo->members[i];
984 		ents[i].tx = m->tx - hot->tx;
985 		ents[i].ty = m->ty - hot->ty;
986 		ents[i].tz = m->tz - hot->tz;
987 		ents[i].shape = m->shapenum;
988 		ents[i].frame = m->framenum;
989 	}
990 	TileRect foot = combo->tilefoot;
991 	int len = Store_u7_comboid(buf, foot.w, foot.h,
992 	                           foot.x + foot.w - 1 - hot->tx,
993 	                           foot.y + foot.h - 1 - hot->ty, cnt, ents);
994 	assert(len <= buflen);
995 	// Set data.
996 	gtk_selection_data_set(seldata,
997 	                       gtk_selection_data_get_target(seldata),
998 	                       8, buf, len);
999 	delete [] buf;
1000 	delete [] ents;
1001 }
1002 
1003 /*
1004  *  Beginning of a drag.
1005  */
1006 
drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer data)1007 gint Combo_chooser::drag_begin(
1008     GtkWidget *widget,      // The view window.
1009     GdkDragContext *context,
1010     gpointer data           // ->Combo_chooser.
1011 ) {
1012 	ignore_unused_variable_warning(widget);
1013 	cout << "In DRAG_BEGIN of Combo" << endl;
1014 	auto *chooser = static_cast<Combo_chooser *>(data);
1015 	if (chooser->selected < 0)
1016 		return FALSE;       // ++++Display a halt bitmap.
1017 	// Get ->combo.
1018 	int num = chooser->info[chooser->selected].num;
1019 	Combo *combo = chooser->combos[num];
1020 	// Show 'hot' member as icon.
1021 	Combo_member *hot = combo->members[combo->hot_index];
1022 	Shape_frame *shape = combo->shapes_file->get_shape(hot->shapenum,
1023 	                     hot->framenum);
1024 	if (shape)
1025 		chooser->set_drag_icon(context, shape);
1026 	return TRUE;
1027 }
1028 
1029 /*
1030  *  Handle a scrollbar event.
1031  */
1032 
scrolled(GtkAdjustment * adj,gpointer data)1033 void Combo_chooser::scrolled(
1034     GtkAdjustment *adj,     // The adjustment.
1035     gpointer data           // ->Combo_chooser.
1036 ) {
1037 	auto *chooser = static_cast<Combo_chooser *>(data);
1038 	gint newindex = static_cast<gint>(gtk_adjustment_get_value(adj));
1039 	chooser->scroll(newindex);
1040 }
1041 
1042 /*
1043  *  Callbacks for controls:
1044  */
1045 /*
1046  *  Keystroke in draw-area.
1047  */
1048 static gboolean
on_combo_key_press(GtkEntry * entry,GdkEventKey * event,gpointer user_data)1049 on_combo_key_press(GtkEntry   *entry,
1050                    GdkEventKey    *event,
1051                    gpointer    user_data) {
1052 	ignore_unused_variable_warning(entry);
1053 	auto *chooser = static_cast<Combo_chooser *>(user_data);
1054 	switch (event->keyval) {
1055 	case GDK_KEY_Delete:
1056 		chooser->remove();
1057 		return TRUE;
1058 	}
1059 	return FALSE;           // Let parent handle it.
1060 }
1061 
1062 /*
1063  *  Enable/disable controls after selection changed.
1064  */
1065 
enable_controls()1066 void Combo_chooser::enable_controls(
1067 ) {
1068 	if (selected == -1) {   // No selection.
1069 		if (!group) {
1070 			gtk_widget_set_sensitive(move_down, false);
1071 			gtk_widget_set_sensitive(move_up, false);
1072 		}
1073 		return;
1074 	}
1075 	if (!group) {
1076 		gtk_widget_set_sensitive(move_down,
1077 		                         info[selected].num < int(combos.size()) - 1);
1078 		gtk_widget_set_sensitive(move_up,
1079 		                         info[selected].num > 0);
1080 	}
1081 }
1082 
1083 static gint Mouse_release(GtkWidget *widget, GdkEventButton *event, gpointer data);
1084 
1085 /*
1086  *  Create the list.
1087  */
1088 
Combo_chooser(Vga_file * i,Flex_file_info * flinfo,unsigned char * palbuf,int w,int h,Shape_group * g)1089 Combo_chooser::Combo_chooser(
1090     Vga_file *i,            // Where they're kept.
1091     Flex_file_info *flinfo,     // Flex-file info.
1092     unsigned char *palbuf,      // Palette, 3*256 bytes (rgb triples).
1093     int w, int h,           // Dimensions.
1094     Shape_group *g          // Filter, or null.
1095 ) : Object_browser(g, flinfo),
1096 	Shape_draw(i, palbuf, gtk_drawing_area_new()),
1097 	flex_info(flinfo), index0(0),
1098 	info(nullptr), info_cnt(0), sel_changed(nullptr) {
1099 	load_internal();             // Init. from file data.
1100 
1101 	// Put things in a vert. box.
1102 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1103 	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
1104 	set_widget(vbox); // This is our "widget"
1105 	gtk_widget_show(vbox);
1106 
1107 	GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1108 	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
1109 	gtk_widget_show(hbox);
1110 	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1111 
1112 	// A frame looks nice.
1113 	GtkWidget *frame = gtk_frame_new(nullptr);
1114 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1115 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1116 	gtk_widget_show(frame);
1117 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
1118 
1119 	// NOTE:  draw is in Shape_draw.
1120 	// Indicate the events we want.
1121 	gtk_widget_set_events(draw, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
1122 	                      | GDK_BUTTON_RELEASE_MASK
1123 	                      | GDK_BUTTON1_MOTION_MASK | GDK_KEY_PRESS_MASK);
1124 	// Set "configure" handler.
1125 	g_signal_connect(G_OBJECT(draw), "configure-event",
1126 	                 G_CALLBACK(configure), this);
1127 	// Set "expose-event" - "draw" handler.
1128 	g_signal_connect(G_OBJECT(draw), "draw",
1129 	                 G_CALLBACK(expose), this);
1130 	// Keystroke.
1131 	g_signal_connect(G_OBJECT(draw), "key-press-event",
1132 	                 G_CALLBACK(on_combo_key_press),
1133 	                 this);
1134 	gtk_widget_set_can_focus(GTK_WIDGET(draw), TRUE);
1135 	// Set mouse click handler.
1136 	g_signal_connect(G_OBJECT(draw), "button-press-event",
1137 	                 G_CALLBACK(mouse_press), this);
1138 	g_signal_connect(G_OBJECT(draw), "button-release-event",
1139 	                 G_CALLBACK(Mouse_release), this);
1140 	// Mouse motion.
1141 	g_signal_connect(G_OBJECT(draw), "drag-begin",
1142 	                 G_CALLBACK(drag_begin), this);
1143 	g_signal_connect(G_OBJECT(draw), "motion-notify-event",
1144 	                 G_CALLBACK(drag_motion), this);
1145 	g_signal_connect(G_OBJECT(draw), "drag-data-get",
1146 	                 G_CALLBACK(drag_data_get), this);
1147 	gtk_container_add(GTK_CONTAINER(frame), draw);
1148 	widget_set_margins(draw, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1149 	gtk_widget_set_size_request(draw, w, h);
1150 	gtk_widget_show(draw);
1151 	// Want a scrollbar for the combos.
1152 	GtkAdjustment *combo_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
1153 	                           combos.size(), 1,
1154 	                           4, 1.0));
1155 	vscroll = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(combo_adj));
1156 	// Update window when it stops.
1157 	// (Deprecated) gtk_range_set_update_policy(GTK_RANGE(vscroll),
1158 	// (Deprecated)                             GTK_UPDATE_DELAYED);
1159 	gtk_box_pack_start(GTK_BOX(hbox), vscroll, FALSE, TRUE, 0);
1160 	// Set scrollbar handler.
1161 	g_signal_connect(G_OBJECT(combo_adj), "value-changed",
1162 	                 G_CALLBACK(scrolled), this);
1163 	gtk_widget_show(vscroll);
1164 
1165 	// At the bottom, status bar:
1166 	GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1167 	gtk_box_set_homogeneous(GTK_BOX(hbox1), FALSE);
1168 	gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
1169 	gtk_widget_show(hbox1);
1170 	// At left, a status bar.
1171 	sbar = gtk_statusbar_new();
1172 	sbar_sel = gtk_statusbar_get_context_id(GTK_STATUSBAR(sbar),
1173 	                                        "selection");
1174 	gtk_box_pack_start(GTK_BOX(hbox1), sbar, TRUE, TRUE, 0);
1175 	widget_set_margins(sbar, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1176 	gtk_widget_show(sbar);
1177 	// Add controls to bottom.
1178 	gtk_box_pack_start(GTK_BOX(vbox),
1179 	                   create_controls(find_controls | move_controls), FALSE, FALSE, 0);
1180 }
1181 
1182 /*
1183  *  Delete.
1184  */
1185 
~Combo_chooser()1186 Combo_chooser::~Combo_chooser(
1187 ) {
1188 	gtk_widget_destroy(get_widget());
1189 	delete [] info;
1190 	int i;
1191 	int cnt = combos.size();
1192 	for (i = 0; i < cnt; i++)   // Delete all the combos.
1193 		delete combos[i];
1194 }
1195 
1196 /*
1197  *  Add a new or updated combo.
1198  *
1199  *  Output: Index of entry.
1200  */
1201 
add(Combo * newcombo,int index)1202 int Combo_chooser::add(
1203     Combo *newcombo,        // We'll own this.
1204     int index           // Index to replace, or -1 to add new.
1205 ) {
1206 	if (index == -1) {
1207 		// New.
1208 		combos.push_back(newcombo);
1209 		index = combos.size() - 1;  // Index of new entry.
1210 	} else {
1211 		assert(index >= 0 && unsigned(index) < combos.size());
1212 		delete combos[index];
1213 		combos[index] = newcombo;
1214 	}
1215 	GtkAdjustment *adj =
1216 	    gtk_range_get_adjustment(GTK_RANGE(vscroll));
1217 	gtk_adjustment_set_upper(adj, combos.size());
1218 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
1219 	render();
1220 	return index;           // Return index.
1221 }
1222 
1223 /*
1224  *  Remove selected entry.
1225  */
1226 
remove()1227 void Combo_chooser::remove(
1228 ) {
1229 	if (selected < 0)
1230 		return;
1231 	int tnum = info[selected].num;
1232 	Combo_editor *combowin = ExultStudio::get_instance()->get_combowin();
1233 	if (combowin && combowin->is_visible() && combowin->file_index == tnum) {
1234 		EStudio::Alert("Can't remove the combo you're editing");
1235 		return;
1236 	}
1237 	if (EStudio::Prompt("Okay to remove selected combo?", "Yes", "no")
1238 	        != 0)
1239 		return;
1240 	selected = -1;
1241 	Combo *todel = combos[tnum];
1242 	delete todel;           // Delete from our list.
1243 	combos.erase(combos.begin() + tnum);
1244 	flex_info->set_modified();
1245 	flex_info->remove(tnum);    // Update flex-file list.
1246 	GtkAdjustment *adj =        // Update scrollbar.
1247 	    gtk_range_get_adjustment(GTK_RANGE(vscroll));
1248 	gtk_adjustment_set_upper(adj, combos.size());
1249 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
1250 	render();
1251 }
1252 
1253 /*
1254  *  Bring up editor for selected combo.
1255  */
1256 
edit()1257 void Combo_chooser::edit(
1258 ) {
1259 	if (selected < 0)
1260 		return;
1261 	Combo_editor *combowin = ExultStudio::get_instance()->get_combowin();
1262 	if (combowin && combowin->is_visible()) {
1263 		EStudio::Alert("You're already editing a combo");
1264 		return;
1265 	}
1266 	int tnum = info[selected].num;
1267 	ExultStudio *studio = ExultStudio::get_instance();
1268 	studio->open_combo_window();    // Open it.
1269 	Combo_editor *ed = studio->get_combowin();
1270 	if (!ed || !ed->is_visible())
1271 		return;         // Failed.  Shouldn't happen.
1272 	ed->set_combo(new Combo(*(combos[tnum])), tnum);
1273 }
1274 
1275 /*
1276  *  Configure the viewing window.
1277  */
1278 
configure(GtkWidget * widget,GdkEventConfigure * event,gpointer data)1279 gint Combo_chooser::configure(
1280     GtkWidget *widget,      // The draw area.
1281     GdkEventConfigure *event,
1282     gpointer data           // ->Combo_chooser
1283 ) {
1284 	ignore_unused_variable_warning(widget);
1285 	auto *chooser = static_cast<Combo_chooser *>(data);
1286 	chooser->Shape_draw::configure();
1287 	chooser->render();
1288 	// Set new scroll amounts.
1289 	int w = event->width;
1290 	int h = event->height;
1291 	int per_row = (w - border) / (128 + border);
1292 	int num_rows = (h - border) / (128 + border);
1293 	int page_size = per_row * num_rows;
1294 	GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(
1295 	                         chooser->vscroll));
1296 	gtk_adjustment_set_step_increment(adj, per_row);
1297 	gtk_adjustment_set_page_increment(adj, page_size);
1298 	gtk_adjustment_set_page_size(adj, page_size);
1299 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
1300 	return TRUE;
1301 }
1302 
1303 /*
1304  *  Handle an expose event.
1305  */
1306 
expose(GtkWidget * widget,cairo_t * cairo,gpointer data)1307 gint Combo_chooser::expose(
1308     GtkWidget *widget,      // The view window.
1309     cairo_t *cairo,
1310     gpointer data           // ->Combo_chooser.
1311 ) {
1312 	ignore_unused_variable_warning(widget);
1313 	auto *chooser = static_cast<Combo_chooser *>(data);
1314 	chooser->set_graphic_context(cairo);
1315 	GdkRectangle area = { 0, 0, 0, 0 };
1316 	gdk_cairo_get_clip_rectangle(cairo, &area);
1317 	chooser->show(area.x, area.y, area.width, area.height);
1318 	chooser->set_graphic_context(nullptr);
1319 	return TRUE;
1320 }
1321 
drag_motion(GtkWidget * widget,GdkEventMotion * event,gpointer data)1322 gint Combo_chooser::drag_motion(
1323     GtkWidget *widget,      // The view window.
1324     GdkEventMotion *event,
1325     gpointer data           // ->Shape_chooser.
1326 ) {
1327 	ignore_unused_variable_warning(widget);
1328 	auto *chooser = static_cast<Combo_chooser *>(data);
1329 	if (!chooser->dragging && chooser->selected >= 0)
1330 		chooser->start_drag(U7_TARGET_COMBOID_NAME,
1331 		                    U7_TARGET_COMBOID, reinterpret_cast<GdkEvent *>(event));
1332 	return true;
1333 }
1334 
1335 /*
1336  *  Handle a mouse button press event.
1337  */
1338 
mouse_press(GtkWidget * widget,GdkEventButton * event,gpointer data)1339 gint Combo_chooser::mouse_press(
1340     GtkWidget *widget,      // The view window.
1341     GdkEventButton *event,
1342     gpointer data           // ->Combo_chooser.
1343 ) {
1344 	gtk_widget_grab_focus(widget);  // Enables keystrokes.
1345 	auto *chooser = static_cast<Combo_chooser *>(data);
1346 
1347 	if (event->button == 4) {
1348 		chooser->scroll(true);
1349 		return TRUE;
1350 	} else if (event->button == 5) {
1351 		chooser->scroll(false);
1352 		return TRUE;
1353 	}
1354 
1355 	int old_selected = chooser->selected;
1356 	int i;              // Search through entries.
1357 	for (i = 0; i < chooser->info_cnt; i++)
1358 		if (chooser->info[i].box.has_point(
1359 		            static_cast<int>(event->x), static_cast<int>(event->y))) {
1360 			// Found the box?
1361 			// Indicate we can drag.
1362 			chooser->selected = i;
1363 			chooser->render();
1364 			// Tell client.
1365 			if (chooser->sel_changed)
1366 				(*chooser->sel_changed)();
1367 			break;
1368 		}
1369 	if (i == chooser->info_cnt && event->button == 1)
1370 		chooser->unselect(true);    // Nothing under mouse.
1371 	else if (chooser->selected == old_selected && old_selected >= 0) {
1372 		// Same square.  Check for dbl-click.
1373 		if (reinterpret_cast<GdkEvent *>(event)->type == GDK_2BUTTON_PRESS)
1374 			chooser->edit();
1375 	}
1376 	if (event->button == 3)
1377 		gtk_menu_popup_at_pointer(GTK_MENU(chooser->create_popup()),
1378 		                          reinterpret_cast<GdkEvent *>(event));
1379 	return TRUE;
1380 }
1381 
1382 /*
1383  *  Handle a mouse button-release event in the combo chooser.
1384  */
Mouse_release(GtkWidget * widget,GdkEventButton * event,gpointer data)1385 static gint Mouse_release(
1386     GtkWidget *widget,      // The view window.
1387     GdkEventButton *event,
1388     gpointer data           // ->Shape_chooser.
1389 ) {
1390 	ignore_unused_variable_warning(widget, event);
1391 	auto *chooser = static_cast<Combo_chooser *>(data);
1392 	chooser->mouse_up();
1393 	return true;
1394 }
1395 
1396 /*
1397  *  Move currently-selected combo up or down.
1398  */
1399 
move(bool upwards)1400 void Combo_chooser::move(
1401     bool upwards
1402 ) {
1403 	if (selected < 0)
1404 		return;         // Shouldn't happen.
1405 	int tnum = info[selected].num;
1406 	if ((tnum == 0 && upwards) || (tnum == int(combos.size()) - 1 && !upwards))
1407 		return;
1408 	if (upwards)            // Going to swap tnum & tnum+1.
1409 		tnum--;
1410 	Combo *tmp = combos[tnum];
1411 	combos[tnum] = combos[tnum + 1];
1412 	combos[tnum + 1] = tmp;
1413 	selected += upwards ? -1 : 1;
1414 	// Update editor if open.
1415 	Combo_editor *combowin = ExultStudio::get_instance()->get_combowin();
1416 	if (combowin && combowin->is_visible()) {
1417 		if (combowin->file_index == tnum)
1418 			combowin->file_index = tnum + 1;
1419 		else if (combowin->file_index == tnum + 1)
1420 			combowin->file_index = tnum;
1421 	}
1422 	flex_info->set_modified();
1423 	flex_info->swap(tnum);      // Update flex-file list.
1424 	render();
1425 }
1426 
1427 /*
1428  *  Search for an entry.
1429  */
1430 
search(const char * srch,int dir)1431 void Combo_chooser::search(
1432     const char *srch,       // What to search for.
1433     int dir             // 1 or -1.
1434 ) {
1435 	int total = get_count();
1436 	if (!total)
1437 		return;         // Empty.
1438 	// Start with selection, or top.
1439 	int start = selected >= 0 ? info[selected].num : 0;
1440 	int i;
1441 	start += dir;
1442 	int stop = dir == -1 ? -1 : total;
1443 	for (i = start; i != stop; i += dir) {
1444 		//int num = group ? (*group)[i] : i;
1445 		const char *nm = combos[i]->name.c_str();
1446 		if (nm && search_name(nm, srch))
1447 			break;      // Found it.
1448 	}
1449 	if (i == stop)
1450 		return;         // Not found.
1451 	while (i < index0)      // Above current view?
1452 		scroll(true);
1453 	while (i >= index0 + info_cnt)  // Below?
1454 		scroll(false);
1455 	int newsel = i - index0;    // New selection.
1456 	if (newsel >= 0 && newsel < info_cnt)
1457 		select(newsel);
1458 	render();
1459 }
1460 
1461