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