1 /**
2  ** A GTK widget showing the list of NPC's.
3  **
4  ** Written: 7/6/2005 - JSF
5  **/
6 
7 /*
8 Copyright (C) 2005-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 "npclst.h"
30 
31 #include "fontgen.h"
32 #include "ibuf8.h"
33 #include "pngio.h"
34 #include "shapefile.h"
35 #include "shapegroup.h"
36 #include "shapevga.h"
37 #include "u7drag.h"
38 #include "utils.h"
39 
40 #include <glib.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 
44 #include <cmath>
45 #include <cstdlib>
46 
47 using EStudio::Add_menu_item;
48 using EStudio::Alert;
49 using std::cout;
50 using std::cerr;
51 using std::endl;
52 using std::string;
53 using std::vector;
54 
55 /*
56  *  Blit onto screen.
57  */
58 
show(int x,int y,int w,int h)59 void Npc_chooser::show(
60     int x, int y, int w, int h  // Area to blit.
61 ) {
62 	Shape_draw::show(x, y, w, h);
63 	if ((selected >= 0) && (drawgc != nullptr)) {    // Show selected.
64 		TileRect b = info[selected].box;
65 		// Draw yellow box.
66 		cairo_set_line_width(drawgc, 1.0);
67 		cairo_set_source_rgb(drawgc,
68 		                     ((drawfg >> 16) & 255) / 255.0,
69 		                     ((drawfg >> 8) & 255) / 255.0,
70 		                     (drawfg & 255) / 255.0);
71 		cairo_rectangle(drawgc, b.x, b.y - voffset, b.w, b.h);
72 		cairo_stroke(drawgc);
73 	}
74 }
75 
76 /*
77  *  Select an entry.  This should be called after rendering
78  *  the shape.
79  */
80 
select(int new_sel)81 void Npc_chooser::select(
82     int new_sel
83 ) {
84 	selected = new_sel;
85 	update_statusbar();
86 }
87 
88 const int border = 4;           // Border at bottom, sides.
89 /*
90  *  Render as many shapes as fit in the shape chooser window.
91  */
92 
render()93 void Npc_chooser::render(
94 ) {
95 	vector<Estudio_npc> &npcs = get_npcs();
96 	// Get drawing area dimensions.
97 	GtkAllocation alloc = {0, 0, 0, 0};
98 	gtk_widget_get_allocation(draw, &alloc);
99 	gint winh = alloc.height;
100 	// Clear window first.
101 	iwin->fill8(255);       // Set to background_color.
102 	int curr_y = -row0_voffset;
103 	//int total_cnt = get_count();
104 	//   filter (group).
105 	for (unsigned rownum = row0; curr_y  < winh && rownum < rows.size();
106 	        ++rownum) {
107 		Npc_row &row = rows[rownum];
108 		unsigned cols = get_num_cols(rownum);
109 		for (unsigned index = row.index0; cols; --cols, ++index) {
110 			int npcnum = info[index].npcnum;
111 			int shapenum = npcs[npcnum].shapenum;
112 			int framenum = info[index].framenum;
113 			Shape_frame *shape = ifile->get_shape(shapenum,
114 			                                      framenum);
115 			if (shape) {
116 				int sx = info[index].box.x - hoffset;
117 				int sy = info[index].box.y - voffset;
118 				shape->paint(iwin, sx + shape->get_xleft(),
119 				             sy + shape->get_yabove());
120 				if (npcs[npcnum].unused)
121 					shape->paint_rle_outline(iwin,
122 					                         sx + shape->get_xleft(),
123 					                         sy + shape->get_yabove(), red);
124 				last_npc = npcnum;
125 			}
126 		}
127 		curr_y += rows[rownum].height;
128 	}
129 	gtk_widget_queue_draw(draw);
130 }
131 
132 /*
133  *  Get maximum shape height for all its frames.
134  */
135 
Get_max_height(Shape * shape)136 static int Get_max_height(
137     Shape *shape
138 ) {
139 	int cnt = shape->get_num_frames();
140 	int maxh = 0;
141 	for (int i = 0; i < cnt; i++) {
142 		Shape_frame *frame = shape->get_frame(i);
143 		if (!frame) {
144 			continue;
145 		}
146 		int ht = frame->get_height();
147 		if (ht > maxh)
148 			maxh = ht;
149 	}
150 	return maxh;
151 }
152 
153 /*
154  *  Get the x-offset in pixels where a frame will be drawn.
155  *
156  *  Output: Offset from left edge of (virtual) drawing area.
157  */
158 
Get_x_offset(Shape * shape,int framenum)159 static int Get_x_offset(
160     Shape *shape,
161     int framenum
162 ) {
163 	if (!shape)
164 		return 0;
165 	int nframes = shape->get_num_frames();
166 	if (framenum >= nframes)
167 		framenum = nframes - 1;
168 	int xoff = 0;
169 	for (int i = 0; i < framenum; i++) {
170 		Shape_frame *fr = shape->get_frame(i);
171 		if (fr) {
172 			xoff += fr->get_width() + border;
173 		}
174 	}
175 	return xoff;
176 }
177 
178 /*
179  *  Find where everything goes.
180  */
181 
setup_info(bool savepos)182 void Npc_chooser::setup_info(
183     bool savepos            // Try to keep current position.
184 ) {
185 	unsigned oldind = rows[row0].index0;
186 	info.resize(0);
187 	rows.resize(0);
188 	row0 = row0_voffset = 0;
189 	last_npc = 0;
190 	/* +++++NOTE:  index0 is always 0 for the NPC browse.  It should
191 	    probably be removed from the base Obj_browser class */
192 	index0 = 0;
193 	voffset = 0;
194 	total_height = 0;
195 	if (frames_mode)
196 		setup_frames_info();
197 	else
198 		setup_shapes_info();
199 	setup_vscrollbar();
200 	if (savepos)
201 		goto_index(oldind);
202 }
203 
204 /*
205  *  Setup info when not in 'frames' mode.
206  */
207 
setup_shapes_info()208 void Npc_chooser::setup_shapes_info(
209 ) {
210 	vector<Estudio_npc> &npcs = get_npcs();
211 	if (npcs.empty())       // No NPC's?  Try to get them.
212 		static_cast<Npcs_file_info *>(file_info)->setup();
213 	int selnpc   = -1;
214 	int selframe = -1;
215 	if (selected >= 0) {    // Save selection info.
216 		selnpc   = info[selected].npcnum;
217 		selframe = info[selected].framenum;
218 	}
219 	// Get drawing area dimensions.
220 	GtkAllocation alloc = {0, 0, 0, 0};
221 	gtk_widget_get_allocation(draw, &alloc);
222 	gint winw = alloc.width;
223 	int x = 0;
224 	int curr_y = 0;
225 	int row_h = 0;
226 	int total_cnt = get_count();
227 	int num_shapes = ifile->get_num_shapes();
228 	//   filter (group).
229 	rows.resize(1);         // Start 1st row.
230 	rows[0].index0 = 0;
231 	rows[0].y = 0;
232 	for (int index = 0; index < total_cnt; index++) {
233 		int npcnum = group ? (*group)[index] : index;
234 		if (npcnum >= 356 && npcnum <= 359)
235 			continue;
236 		int shapenum = npcs[npcnum].shapenum;
237 		int framenum = npcnum == selnpc ? selframe : framenum0;
238 		if (shapenum < 0 || shapenum >= num_shapes)
239 			continue;
240 		Shape_frame *shape = ifile->get_shape(shapenum, framenum);
241 		if (!shape)
242 			continue;
243 		int sh = shape->get_height();
244 		int sw = shape->get_width();
245 		// Check if we've exceeded max width
246 		if (x + sw > winw && x) {   // But don't leave row empty.
247 			// Next line.
248 			rows.back().height = row_h + border;
249 			curr_y += row_h + border;
250 			row_h = 0;
251 			x = 0;
252 			rows.emplace_back();
253 			rows.back().index0 = info.size();
254 			rows.back().y = curr_y;
255 		}
256 		if (sh > row_h)
257 			row_h = sh;
258 		int sy = curr_y + border; // Get top y-coord.
259 		// Store info. about where drawn.
260 		info.emplace_back();
261 		info.back().set(npcnum, framenum, x, sy, sw, sh);
262 		x += sw + border;
263 	}
264 	rows.back().height = row_h + border;
265 	total_height = curr_y + rows.back().height + border;
266 }
267 
268 /*
269  *  Setup one NPC per row, showing its frames from left to right.
270  */
271 
setup_frames_info()272 void Npc_chooser::setup_frames_info(
273 ) {
274 	vector<Estudio_npc> &npcs = get_npcs();
275 	if (npcs.empty())       // No NPC's?  Try to get them.
276 		static_cast<Npcs_file_info *>(file_info)->setup();
277 	// Get drawing area dimensions.
278 	int curr_y = 0;
279 	int maxw = 0;
280 	unsigned total_cnt = get_count();
281 	int num_shapes = ifile->get_num_shapes();
282 	//   filter (group).
283 	for (unsigned index = index0; index < total_cnt; index++) {
284 		int npcnum = group ? (*group)[index] : index;
285 		if (npcnum >= 356 && npcnum <= 359)
286 			continue;
287 		int shapenum = npcs[npcnum].shapenum;
288 		if (shapenum < 0 || shapenum >= num_shapes)
289 			continue;
290 		// Get all frames.
291 		Shape *shape = ifile->extract_shape(shapenum);
292 		int nframes = shape ? shape->get_num_frames() : 0;
293 		if (!nframes)
294 			continue;
295 		int row_h = Get_max_height(shape);
296 		rows.emplace_back();
297 		rows.back().index0 = info.size();
298 		rows.back().y = curr_y;
299 		rows.back().height = row_h + border;
300 		int x = 0;
301 		int sw;
302 		int sh;
303 		for (int framenum = 0; framenum < nframes; framenum++,
304 		        x += sw + border) {
305 			Shape_frame *frame = shape->get_frame(framenum);
306 			if (!frame) {
307 				sw = 0;
308 				continue;
309 			}
310 			sh = frame->get_height();
311 			sw = frame->get_width();
312 			int sy = curr_y + border; // Get top y-coord.
313 			// Store info. about where drawn.
314 			info.emplace_back();
315 			info.back().set(npcnum, framenum, x, sy, sw, sh);
316 		}
317 		if (x > maxw)
318 			maxw = x;
319 		// Next line.
320 		curr_y += row_h + border;
321 	}
322 	total_height = curr_y + border;
323 	setup_hscrollbar(maxw);
324 }
325 
326 /*
327  *  Horizontally scroll so that the selected frame is visible (in frames
328  *  mode).
329  */
330 
scroll_to_frame()331 void Npc_chooser::scroll_to_frame(
332 ) {
333 	if (selected >= 0) {    // Save selection info.
334 		vector<Estudio_npc> &npcs = get_npcs();
335 		int selnpc   = info[selected].npcnum;
336 		int selframe = info[selected].framenum;
337 		Shape *shape = ifile->extract_shape(npcs[selnpc].shapenum);
338 		int xoff = Get_x_offset(shape, selframe);
339 		if (xoff < hoffset) // Left of visual area?
340 			hoffset = xoff > border ? xoff - border : 0;
341 		else {
342 			GtkAllocation alloc = {0, 0, 0, 0};
343 			gtk_widget_get_allocation(draw, &alloc);
344 			gint winw = alloc.width;
345 			Shape_frame *fr = shape->get_frame(selframe);
346 			if (fr) {
347 				int sw = fr->get_width();
348 				if (xoff + sw + border - hoffset > winw)
349 					hoffset = xoff + sw + border - winw;
350 			}
351 		}
352 		GtkAdjustment *adj = gtk_range_get_adjustment(
353 		                         GTK_RANGE(hscroll));
354 		gtk_adjustment_set_value(adj, hoffset);
355 	}
356 }
357 
358 /*
359  *  Scroll so a desired index is in view.
360  */
361 
goto_index(unsigned index)362 void Npc_chooser::goto_index(
363     unsigned index           // Desired index in 'info'.
364 ) {
365 	if (index >= info.size())
366 		return;         // Illegal index or empty chooser.
367 	Npc_entry &inf = info[index];   // Already in view?
368 	int midx = inf.box.x + inf.box.w / 2;
369 	int midy = inf.box.y + inf.box.h / 2;
370 	TileRect winrect(0, voffset, config_width, config_height);
371 	if (winrect.has_point(midx, midy))
372 		return;
373 	unsigned start = 0;
374 	unsigned count = rows.size();
375 	while (count > 1) {     // Binary search.
376 		unsigned mid = start + count / 2;
377 		if (index < rows[mid].index0)
378 			count = mid - start;
379 		else {
380 			count = (start + count) - mid;
381 			start = mid;
382 		}
383 	}
384 	if (start < rows.size()) {
385 		// Get to right spot again!
386 		GtkAdjustment *adj = gtk_range_get_adjustment(
387 		                         GTK_RANGE(vscroll));
388 		gtk_adjustment_set_value(adj, rows[start].y);
389 	}
390 }
391 
392 /*
393  *  Find index for a given NPC #.
394  */
395 
find_npc(int npcnum)396 int Npc_chooser::find_npc(
397     int npcnum
398 ) {
399 	if (group) {        // They're not ordered.
400 		int cnt = info.size();
401 		for (int i = 0; i < cnt; ++i)
402 			if (info[i].npcnum == npcnum)
403 				return i;
404 		return -1;
405 	}
406 	unsigned start = 0;
407 	unsigned count = info.size();
408 	while (count > 1) {     // Binary search.
409 		unsigned mid = start + count / 2;
410 		if (npcnum < info[mid].npcnum)
411 			count = mid - start;
412 		else {
413 			count = (start + count) - mid;
414 			start = mid;
415 		}
416 	}
417 	if (start < info.size())
418 		return start;
419 	else
420 		return -1;
421 }
422 
423 /*
424  *  Configure the viewing window.
425  */
426 
Configure_chooser(GtkWidget * widget,GdkEventConfigure * event,gpointer data)427 static gint Configure_chooser(
428     GtkWidget *widget,      // The drawing area.
429     GdkEventConfigure *event,
430     gpointer data           // ->Npc_chooser
431 ) {
432 	ignore_unused_variable_warning(widget);
433 	auto *chooser = static_cast<Npc_chooser *>(data);
434 	return chooser->configure(event);
435 }
configure(GdkEventConfigure * event)436 gint Npc_chooser::configure(
437     GdkEventConfigure *event
438 ) {
439 	Shape_draw::configure();
440 	// Did the size change?
441 	if (event->width != config_width || event->height != config_height) {
442 		config_width = event->width;
443 		config_height = event->height;
444 		setup_info(true);
445 		render();
446 		update_statusbar();
447 	} else
448 		render();       // Same size?  Just render it.
449 	if (group)          // Filtering?
450 		enable_drop();      // Can drop NPCs here.
451 	return TRUE;
452 }
453 
454 /*
455  *  Handle an expose event.
456  */
457 
expose(GtkWidget * widget,cairo_t * cairo,gpointer data)458 gint Npc_chooser::expose(
459     GtkWidget *widget,      // The view window.
460     cairo_t *cairo,
461     gpointer data           // ->Npc_chooser.
462 ) {
463 	ignore_unused_variable_warning(widget);
464 	auto *chooser = static_cast<Npc_chooser *>(data);
465 	chooser->set_graphic_context(cairo);
466 	GdkRectangle area = { 0, 0, 0, 0 };
467 	gdk_cairo_get_clip_rectangle(cairo, &area);
468 	chooser->show(area.x, area.y, area.width, area.height);
469 	chooser->set_graphic_context(nullptr);
470 	return TRUE;
471 }
472 
473 /*
474  *  Handle a mouse drag event.
475  */
476 
drag_motion(GtkWidget * widget,GdkEventMotion * event,gpointer data)477 gint Npc_chooser::drag_motion(
478     GtkWidget *widget,      // The view window.
479     GdkEventMotion *event,
480     gpointer data           // ->Npc_chooser.
481 ) {
482 	ignore_unused_variable_warning(widget);
483 	auto *chooser = static_cast<Npc_chooser *>(data);
484 	if (!chooser->dragging && chooser->selected >= 0)
485 		chooser->start_drag(U7_TARGET_NPCID_NAME,
486 		                    U7_TARGET_NPCID, reinterpret_cast<GdkEvent *>(event));
487 	return true;
488 }
489 
490 /*
491  *  Handle a mouse button-press event.
492  */
mouse_press(GtkWidget * widget,GdkEventButton * event)493 gint Npc_chooser::mouse_press(
494     GtkWidget *widget,      // The view window.
495     GdkEventButton *event
496 ) {
497 	gtk_widget_grab_focus(widget);
498 
499 	if (event->button == 4) {
500 		if (row0 > 0)
501 			scroll_row_vertical(row0 - 1);
502 		return TRUE;
503 	} else if (event->button == 5) {
504 		scroll_row_vertical(row0 + 1);
505 		return TRUE;
506 	}
507 	int old_selected = selected;
508 	int new_selected = -1;
509 	unsigned i;              // Search through entries.
510 	unsigned infosz = info.size();
511 	int absx = static_cast<int>(event->x);
512 	int absy = static_cast<int>(event->y) + voffset;
513 	for (i = rows[row0].index0; i < infosz; i++) {
514 		if (info[i].box.has_point(absx, absy)) {
515 			// Found the box?
516 			// Indicate we can drag.
517 			new_selected = i;
518 			break;
519 		} else if (info[i].box.y - voffset >= config_height)
520 			break;      // Past bottom of screen.
521 	}
522 	if (new_selected >= 0) {
523 		select(new_selected);
524 		render();
525 		if (sel_changed)    // Tell client.
526 			(*sel_changed)();
527 	}
528 	if (new_selected < 0 && event->button == 1)
529 		unselect(true);     // No selection.
530 	else if (selected == old_selected && old_selected >= 0) {
531 		// Same square.  Check for dbl-click.
532 		if (reinterpret_cast<GdkEvent *>(event)->type == GDK_2BUTTON_PRESS)
533 			edit_npc();
534 	}
535 	if (event->button == 3)
536 		gtk_menu_popup_at_pointer(GTK_MENU(create_popup()),
537 		                          reinterpret_cast<GdkEvent *>(event));
538 	return TRUE;
539 }
540 
541 /*
542  *  Handle mouse button press/release events.
543  */
Mouse_press(GtkWidget * widget,GdkEventButton * event,gpointer data)544 static gint Mouse_press(
545     GtkWidget *widget,      // The view window.
546     GdkEventButton *event,
547     gpointer data           // ->Npc_chooser.
548 ) {
549 	auto *chooser = static_cast<Npc_chooser *>(data);
550 	return chooser->mouse_press(widget, event);
551 }
Mouse_release(GtkWidget * widget,GdkEventButton * event,gpointer data)552 static gint Mouse_release(
553     GtkWidget *widget,      // The view window.
554     GdkEventButton *event,
555     gpointer data           // ->Npc_chooser.
556 ) {
557 	ignore_unused_variable_warning(widget, event);
558 	auto *chooser = static_cast<Npc_chooser *>(data);
559 	chooser->mouse_up();
560 	return true;
561 }
562 
563 /*
564  *  Keystroke in draw-area.
565  */
566 C_EXPORT gboolean
on_npc_draw_key_press(GtkEntry * entry,GdkEventKey * event,gpointer user_data)567 on_npc_draw_key_press(GtkEntry   *entry,
568                       GdkEventKey    *event,
569                       gpointer    user_data) {
570 	ignore_unused_variable_warning(entry, event, user_data);
571 	//Npc_chooser *chooser = static_cast<Npc_chooser *>(user_data);
572 	return FALSE;           // Let parent handle it.
573 }
574 
575 /*
576  *  Bring up the NPC editor.
577  */
578 
edit_npc()579 void Npc_chooser::edit_npc(
580 ) {
581 	ExultStudio *studio = ExultStudio::get_instance();
582 	int npcnum = info[selected].npcnum;
583 	//Estudio_npc &npc = get_npcs()[npcnum];
584 	unsigned char buf[Exult_server::maxlength];
585 	unsigned char *ptr;
586 	ptr = &buf[0];
587 	Write2(ptr, npcnum);
588 	if (!studio->send_to_server(Exult_server::edit_npc, buf, ptr - buf))
589 		cerr << "Error sending data to server." << endl;
590 	const gchar *const *locales = g_get_language_names();
591 	if (!locales) {
592 		cerr << "No locales!" << endl;
593 		return;
594 	}
595 	while (*locales) {
596 		cerr << "\"" << *locales << "\"" << endl;
597 		locales++;
598 	}
599 }
600 
601 /*
602  *  Update NPC information.
603  */
604 
update_npc(int num)605 void Npc_chooser::update_npc(
606     int num
607 ) {
608 	static_cast<Npcs_file_info *>(file_info)->read_npc(num);
609 	render();
610 	update_statusbar();
611 }
612 
613 /*
614  *  Someone wants the dragged shape.
615  */
616 
drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * seldata,guint info,guint time,gpointer data)617 void Npc_chooser::drag_data_get(
618     GtkWidget *widget,      // The view window.
619     GdkDragContext *context,
620     GtkSelectionData *seldata,  // Fill this in.
621     guint info,
622     guint time,
623     gpointer data           // ->Npc_chooser.
624 ) {
625 	ignore_unused_variable_warning(widget, context, time);
626 	cout << "In DRAG_DATA_GET of Npc for '"
627 	     << gdk_atom_name(gtk_selection_data_get_target(seldata))
628 	     << "'" << endl;
629 	auto *chooser = static_cast<Npc_chooser *>(data);
630 	if (chooser->selected < 0 || info != U7_TARGET_NPCID)
631 		return;         // Not sure about this.
632 	guchar buf[U7DND_DATA_LENGTH(1)];
633 	int npcnum = chooser->info[chooser->selected].npcnum;
634 	int len = Store_u7_npcid(buf, npcnum);
635 	cout << "Setting selection data (" << npcnum << ')' << endl;
636 	// Set data.
637 	gtk_selection_data_set(seldata,
638 	                       gtk_selection_data_get_target(seldata),
639 	                       8, buf, len);
640 }
641 
642 /*
643  *  Beginning of a drag.
644  */
645 
drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer data)646 gint Npc_chooser::drag_begin(
647     GtkWidget *widget,      // The view window.
648     GdkDragContext *context,
649     gpointer data           // ->Npc_chooser.
650 ) {
651 	ignore_unused_variable_warning(widget);
652 	cout << "In DRAG_BEGIN of Npc" << endl;
653 	auto *chooser = static_cast<Npc_chooser *>(data);
654 	if (chooser->selected < 0)
655 		return FALSE;       // ++++Display a halt bitmap.
656 	// Get ->npc.
657 	int npcnum = chooser->info[chooser->selected].npcnum;
658 	Estudio_npc &npc = chooser->get_npcs()[npcnum];
659 	Shape_frame *shape = chooser->ifile->get_shape(npc.shapenum, 0);
660 	if (!shape)
661 		return FALSE;
662 	chooser->set_drag_icon(context, shape); // Set icon for dragging.
663 	return TRUE;
664 }
665 
666 /*
667  *  Chunk was dropped here.
668  */
669 
drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * seldata,guint info,guint time,gpointer udata)670 void Npc_chooser::drag_data_received(
671     GtkWidget *widget,
672     GdkDragContext *context,
673     gint x,
674     gint y,
675     GtkSelectionData *seldata,
676     guint info,
677     guint time,
678     gpointer udata          // -> Npc_chooser.
679 ) {
680 	ignore_unused_variable_warning(widget, context, x, y, info, time);
681 	auto *chooser = static_cast<Npc_chooser *>(udata);
682 	cout << "In DRAG_DATA_RECEIVED of Npc for '"
683 	     << gdk_atom_name(gtk_selection_data_get_data_type(seldata))
684 	     << "'" << endl;
685 	if ((gtk_selection_data_get_data_type(seldata) == gdk_atom_intern(U7_TARGET_NPCID_NAME, 0) ||
686 	     gtk_selection_data_get_data_type(seldata) == gdk_atom_intern(U7_TARGET_DROPFILE_NAME_MIME, 0) ||
687 	     gtk_selection_data_get_data_type(seldata) == gdk_atom_intern(U7_TARGET_DROPFILE_NAME_MACOSX, 0)) &&
688 	        Is_u7_npcid(gtk_selection_data_get_data(seldata)) &&
689 	        gtk_selection_data_get_format(seldata) == 8 &&
690 	        gtk_selection_data_get_length(seldata) > 0) {
691 		int npcnum;
692 		Get_u7_npcid(gtk_selection_data_get_data(seldata), npcnum);
693 		chooser->group->add(npcnum);
694 		chooser->setup_info(true);
695 		chooser->render();
696 	}
697 }
698 
699 /*
700  *  Set to accept drops from drag-n-drop of a chunk.
701  */
702 
enable_drop()703 void Npc_chooser::enable_drop(
704 ) {
705 	if (drop_enabled)       // More than once causes warning.
706 		return;
707 	drop_enabled = true;
708 	gtk_widget_realize(draw);//???????
709 	GtkTargetEntry tents[3];
710 	tents[0].target = const_cast<char *>(U7_TARGET_NPCID_NAME);
711 	tents[1].target = const_cast<char *>(U7_TARGET_DROPFILE_NAME_MIME);
712 	tents[2].target = const_cast<char *>(U7_TARGET_DROPFILE_NAME_MACOSX);
713 	tents[0].flags = 0;
714 	tents[1].flags = 0;
715 	tents[2].flags = 0;
716 	tents[0].info = U7_TARGET_NPCID;
717 	tents[1].info = U7_TARGET_NPCID;
718 	tents[2].info = U7_TARGET_NPCID;
719 	gtk_drag_dest_set(draw, GTK_DEST_DEFAULT_ALL, tents, 3,
720 	                  static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE));
721 
722 	g_signal_connect(G_OBJECT(draw), "drag-data-received",
723 	                 G_CALLBACK(drag_data_received), this);
724 }
725 
726 /*
727  *  Scroll to a new shape/frame.
728  */
729 
scroll_row_vertical(unsigned newrow)730 void Npc_chooser::scroll_row_vertical(
731     unsigned newrow          // Abs. index of row to show.
732 ) {
733 	if (newrow >= rows.size())
734 		return;
735 	row0 = newrow;
736 	row0_voffset = 0;
737 	render();
738 }
739 
740 /*
741  *  Scroll to new pixel offset.
742  */
743 
scroll_vertical(int newoffset)744 void Npc_chooser::scroll_vertical(
745     int newoffset
746 ) {
747 	int delta = newoffset - voffset;
748 	while (delta > 0 && row0 < rows.size() - 1) {
749 		// Going down.
750 		int rowh = rows[row0].height - row0_voffset;
751 		if (delta < rowh) {
752 			// Part of current row.
753 			voffset += delta;
754 			row0_voffset += delta;
755 			delta = 0;
756 		} else {
757 			// Go down to next row.
758 			voffset += rowh;
759 			delta -= rowh;
760 			++row0;
761 			row0_voffset = 0;
762 		}
763 	}
764 	while (delta < 0) {
765 		if (-delta <= row0_voffset) {
766 			voffset += delta;
767 			row0_voffset += delta;
768 			delta = 0;
769 		} else if (row0_voffset) {
770 			voffset -= row0_voffset;
771 			delta += row0_voffset;
772 			row0_voffset = 0;
773 		} else {
774 			if (row0 == 0)
775 				break;
776 			--row0;
777 			row0_voffset = 0;
778 			voffset -= rows[row0].height;
779 			delta += rows[row0].height;
780 			if (delta > 0) {
781 				row0_voffset = delta;
782 				voffset += delta;
783 				delta = 0;
784 			}
785 		}
786 	}
787 	render();
788 	update_statusbar();
789 }
790 
791 /*
792  *  Adjust vertical scroll amounts after laying out shapes.
793  */
794 
setup_vscrollbar()795 void Npc_chooser::setup_vscrollbar(
796 ) {
797 	GtkAdjustment *adj = gtk_range_get_adjustment(
798 	                         GTK_RANGE(vscroll));
799 	gtk_adjustment_set_value(adj, 0);
800 	gtk_adjustment_set_lower(adj, 0);
801 	gtk_adjustment_set_upper(adj, total_height);
802 	gtk_adjustment_set_step_increment(adj, 16);   // +++++FOR NOW.
803 	gtk_adjustment_set_page_increment(adj, config_height);
804 	gtk_adjustment_set_page_size(adj, config_height);
805 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
806 }
807 
808 /*
809  *  Adjust horizontal scroll amounts.
810  */
811 
setup_hscrollbar(int newmax)812 void Npc_chooser::setup_hscrollbar(
813     int newmax          // New max., or -1 to leave alone.
814 ) {
815 	GtkAdjustment *adj = gtk_range_get_adjustment(
816 	                         GTK_RANGE(hscroll));
817 	if (newmax > 0)
818 		gtk_adjustment_set_upper(adj, newmax);
819 	GtkAllocation alloc = {0, 0, 0, 0};
820 	gtk_widget_get_allocation(draw, &alloc);
821 	gtk_adjustment_set_page_increment(adj, alloc.width);
822 	gtk_adjustment_set_page_size(adj, alloc.width);
823 	if (gtk_adjustment_get_page_size(adj) > gtk_adjustment_get_upper(adj))
824 		gtk_adjustment_set_upper(adj, gtk_adjustment_get_page_size(adj));
825 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
826 }
827 
828 /*
829  *  Handle a scrollbar event.
830  */
831 
vscrolled(GtkAdjustment * adj,gpointer data)832 void Npc_chooser::vscrolled(    // For vertical scrollbar.
833     GtkAdjustment *adj,     // The adjustment.
834     gpointer data           // ->Npc_chooser.
835 ) {
836 	auto *chooser = static_cast<Npc_chooser *>(data);
837 	cout << "Scrolled to " << gtk_adjustment_get_value(adj) << '\n';
838 	gint newindex = static_cast<gint>(gtk_adjustment_get_value(adj));
839 	chooser->scroll_vertical(newindex);
840 }
hscrolled(GtkAdjustment * adj,gpointer data)841 void Npc_chooser::hscrolled(      // For horizontal scrollbar.
842     GtkAdjustment *adj,     // The adjustment.
843     gpointer data           // ->Npc_chooser.
844 ) {
845 	auto *chooser = static_cast<Npc_chooser *>(data);
846 	chooser->hoffset = static_cast<gint>(gtk_adjustment_get_value(adj));
847 	chooser->render();
848 }
849 
850 /*
851  *  Handle a change to the 'frame' spin button.
852  */
853 
frame_changed(GtkAdjustment * adj,gpointer data)854 void Npc_chooser::frame_changed(
855     GtkAdjustment *adj,     // The adjustment.
856     gpointer data           // ->Npc_chooser.
857 ) {
858 	auto *chooser = static_cast<Npc_chooser *>(data);
859 	gint newframe = static_cast<gint>(gtk_adjustment_get_value(adj));
860 	if (chooser->selected >= 0) {
861 		Npc_entry &npcinfo = chooser->info[chooser->selected];
862 		vector<Estudio_npc> &npcs = chooser->get_npcs();
863 		int nframes = chooser->ifile->get_num_frames(npcs[npcinfo.npcnum].shapenum);
864 		if (newframe >= nframes)    // Just checking
865 			return;
866 		npcinfo.framenum = newframe;
867 		if (chooser->frames_mode)   // Get sel. frame in view.
868 			chooser->scroll_to_frame();
869 		chooser->render();
870 		chooser->update_statusbar();
871 	}
872 }
873 
874 /*
875  *  'All frames' toggled.
876  */
877 
all_frames_toggled(GtkToggleButton * btn,gpointer data)878 void Npc_chooser::all_frames_toggled(
879     GtkToggleButton *btn,
880     gpointer data
881 ) {
882 	auto *chooser = static_cast<Npc_chooser *>(data);
883 	if (chooser->info.empty()) return;
884 	bool on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn));
885 	chooser->frames_mode = on;
886 	if (on)             // Frame => show horiz. scrollbar.
887 		gtk_widget_show(chooser->hscroll);
888 	else
889 		gtk_widget_hide(chooser->hscroll);
890 	// The old index is no longer valid, so we need to remember the shape.
891 	int indx = chooser->selected >= 0 ? chooser->selected
892 	           : static_cast<int>(chooser->rows[chooser->row0].index0);
893 	int npcnum = chooser->info[indx].npcnum;
894 	chooser->selected = -1;
895 	chooser->setup_info();
896 	indx = chooser->find_npc(npcnum);
897 	if (indx >= 0)          // Get back to given shape.
898 		chooser->goto_index(indx);
899 }
900 
901 /*
902  *  Get # shapes we can display.
903  */
904 
get_count()905 int Npc_chooser::get_count(
906 ) {
907 	return group ? group->size() : get_npcs().size();
908 }
909 
910 /*
911  *  Get NPC list.
912  */
913 
get_npcs()914 vector<Estudio_npc> &Npc_chooser::get_npcs(
915 ) {
916 	return static_cast<Npcs_file_info *>(file_info)->get_npcs();
917 }
918 
919 /*
920  *  Search for an entry.
921  */
922 
search(const char * srch,int dir)923 void Npc_chooser::search(
924     const char *srch,       // What to search for.
925     int dir             // 1 or -1.
926 ) {
927 	int total = get_count();
928 	if (!total)
929 		return;         // Empty.
930 	vector<Estudio_npc> &npcs = get_npcs();
931 	// Start with selection, or top.
932 	int start = selected >= 0 ? selected : static_cast<int>(rows[row0].index0);
933 	int i;
934 	start += dir;
935 	int stop = dir == -1 ? -1 : static_cast<int>(info.size());
936 	for (i = start; i != stop; i += dir) {
937 		unsigned npcnum = info[i].npcnum;
938 		const char *nm = npcnum < npcs.size() ?
939 		                 npcs[npcnum].name.c_str() : nullptr;
940 		if (nm && search_name(nm, srch))
941 			break;      // Found it.
942 	}
943 	if (i == stop)
944 		return;         // Not found.
945 	goto_index(i);
946 	select(i);
947 	render();
948 }
949 
950 /*
951  *  Locate NPC on game map.
952  */
953 
locate(bool upwards)954 void Npc_chooser::locate(
955     bool upwards
956 ) {
957 	ignore_unused_variable_warning(upwards);
958 	if (selected < 0)
959 		return;         // Shouldn't happen.
960 	int npcnum = info[selected].npcnum;
961 	if (get_npcs()[npcnum].unused) {
962 		EStudio::Alert("Npc %d is unused.", npcnum);
963 		return;
964 	}
965 	unsigned char data[Exult_server::maxlength];
966 	unsigned char *ptr = &data[0];
967 	Write2(ptr, npcnum);
968 	ExultStudio *studio = ExultStudio::get_instance();
969 	studio->send_to_server(
970 	    Exult_server::locate_npc, data, ptr - data);
971 }
972 
973 /*
974  *  Handle popup menu items.
975  */
976 
on_npc_popup_edit_activate(GtkMenuItem * item,gpointer udata)977 void on_npc_popup_edit_activate(
978     GtkMenuItem *item,
979     gpointer udata
980 ) {
981 	ignore_unused_variable_warning(item);
982 	static_cast<Npc_chooser *>(udata)->edit_npc();
983 }
984 
985 /*
986  *  Set up popup menu for shape browser.
987  */
988 
create_popup()989 GtkWidget *Npc_chooser::create_popup(
990 ) {
991 	// Create popup with groups, but not files.
992 	create_popup_internal(false);
993 	if (selected >= 0) {    // Add editing choices.
994 		Add_menu_item(popup, "Edit...",
995 		              G_CALLBACK(on_npc_popup_edit_activate),
996 		              this);
997 	}
998 	return popup;
999 }
1000 
1001 /*
1002  *  Create the list.
1003  */
1004 
Npc_chooser(Vga_file * i,unsigned char * palbuf,int w,int h,Shape_group * g,Shape_file_info * fi)1005 Npc_chooser::Npc_chooser(
1006     Vga_file *i,            // Where they're kept.
1007     unsigned char *palbuf,      // Palette, 3*256 bytes (rgb triples).
1008     int w, int h,           // Dimensions.
1009     Shape_group *g,
1010     Shape_file_info *fi
1011 ) : Object_browser(g, fi),
1012 	Shape_draw(i, palbuf, gtk_drawing_area_new()),
1013 	framenum0(0),
1014 	info(0), rows(0), row0(0),
1015 	row0_voffset(0), total_height(0),
1016 	frames_mode(false), hoffset(0),
1017 	voffset(0), status_id(-1), drop_enabled(false), sel_changed(nullptr) {
1018 	rows.reserve(40);
1019 
1020 	// Put things in a vert. box.
1021 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1022 	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
1023 	set_widget(vbox); // This is our "widget"
1024 	gtk_widget_show(vbox);
1025 
1026 	GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1027 	gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
1028 	gtk_widget_show(hbox);
1029 	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1030 
1031 	// A frame looks nice.
1032 	GtkWidget *frame = gtk_frame_new(nullptr);
1033 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
1034 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1035 	gtk_widget_show(frame);
1036 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
1037 
1038 	// NOTE:  draw is in Shape_draw.
1039 	// Indicate the events we want.
1040 	gtk_widget_set_events(draw, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
1041 	                      | GDK_BUTTON_RELEASE_MASK
1042 	                      | GDK_BUTTON1_MOTION_MASK | GDK_KEY_PRESS_MASK);
1043 	// Set "configure" handler.
1044 	g_signal_connect(G_OBJECT(draw), "configure-event",
1045 	                 G_CALLBACK(Configure_chooser), this);
1046 	// Set "expose-event" - "draw" handler.
1047 	g_signal_connect(G_OBJECT(draw), "draw",
1048 	                 G_CALLBACK(expose), this);
1049 	// Keystroke.
1050 	g_signal_connect(G_OBJECT(draw), "key-press-event",
1051 	                 G_CALLBACK(on_npc_draw_key_press),
1052 	                 this);
1053 	gtk_widget_set_can_focus(GTK_WIDGET(draw), TRUE);
1054 	// Set mouse click handler.
1055 	g_signal_connect(G_OBJECT(draw), "button-press-event",
1056 	                 G_CALLBACK(Mouse_press), this);
1057 	g_signal_connect(G_OBJECT(draw), "button-release-event",
1058 	                 G_CALLBACK(Mouse_release), this);
1059 	// Mouse motion.
1060 	g_signal_connect(G_OBJECT(draw), "drag-begin",
1061 	                 G_CALLBACK(drag_begin), this);
1062 	g_signal_connect(G_OBJECT(draw), "motion-notify-event",
1063 	                 G_CALLBACK(drag_motion), this);
1064 	g_signal_connect(G_OBJECT(draw), "drag-data-get",
1065 	                 G_CALLBACK(drag_data_get), this);
1066 	gtk_container_add(GTK_CONTAINER(frame), draw);
1067 	widget_set_margins(draw, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1068 	gtk_widget_set_size_request(draw, w, h);
1069 	gtk_widget_show(draw);
1070 	// Want vert. scrollbar for the shapes.
1071 	GtkAdjustment *shape_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
1072 	                           std::ceil(get_count() / 4.0), 1, 1, 1));
1073 	vscroll = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(shape_adj));
1074 	gtk_box_pack_start(GTK_BOX(hbox), vscroll, FALSE, TRUE, 0);
1075 	// Set scrollbar handler.
1076 	g_signal_connect(G_OBJECT(shape_adj), "value-changed",
1077 	                 G_CALLBACK(vscrolled), this);
1078 	gtk_widget_show(vscroll);
1079 	// Horizontal scrollbar.
1080 	shape_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 1600, 8, 16, 16));
1081 	hscroll = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(shape_adj));
1082 	gtk_box_pack_start(GTK_BOX(vbox), hscroll, FALSE, TRUE, 0);
1083 	// Set scrollbar handler.
1084 	g_signal_connect(G_OBJECT(shape_adj), "value-changed",
1085 	                 G_CALLBACK(hscrolled), this);
1086 //++++  gtk_widget_hide(hscroll);   // Only shown in 'frames' mode.
1087 
1088 	// At the bottom, status bar & frame:
1089 	GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1090 	gtk_box_set_homogeneous(GTK_BOX(hbox1), FALSE);
1091 	gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
1092 	gtk_widget_show(hbox1);
1093 	// At left, a status bar.
1094 	sbar = gtk_statusbar_new();
1095 	sbar_sel = gtk_statusbar_get_context_id(GTK_STATUSBAR(sbar),
1096 	                                        "selection");
1097 	gtk_box_pack_start(GTK_BOX(hbox1), sbar, TRUE, TRUE, 0);
1098 	widget_set_margins(sbar, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1099 	gtk_widget_show(sbar);
1100 	GtkWidget *label = gtk_label_new("Frame:");
1101 	gtk_box_pack_start(GTK_BOX(hbox1), label, FALSE, FALSE, 0);
1102 	widget_set_margins(label, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1103 	gtk_widget_show(label);
1104 	// A spin button for frame#.
1105 	frame_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
1106 	                           16, 1,
1107 	                           0, 0.0));
1108 	fspin = gtk_spin_button_new(GTK_ADJUSTMENT(frame_adj),
1109 	                            1, 0);
1110 	g_signal_connect(G_OBJECT(frame_adj), "value-changed",
1111 	                 G_CALLBACK(frame_changed), this);
1112 	gtk_box_pack_start(GTK_BOX(hbox1), fspin, FALSE, FALSE, 0);
1113 	widget_set_margins(fspin, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1114 	gtk_widget_show(fspin);
1115 	// A toggle for 'All Frames'.
1116 	GtkWidget *allframes = gtk_toggle_button_new_with_label("Frames");
1117 	gtk_box_pack_start(GTK_BOX(hbox1), allframes, FALSE, FALSE, 0);
1118 	widget_set_margins(allframes, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
1119 	gtk_widget_show(allframes);
1120 	g_signal_connect(G_OBJECT(allframes), "toggled",
1121 	                 G_CALLBACK(all_frames_toggled), this);
1122 	// Add search controls to bottom.
1123 	gtk_box_pack_start(GTK_BOX(vbox),
1124 	                   create_controls(find_controls | locate_controls),
1125 	                   FALSE, FALSE, 0);
1126 	red = ExultStudio::get_instance()->find_palette_color(63, 5, 5);
1127 }
1128 
1129 /*
1130  *  Delete.
1131  */
1132 
~Npc_chooser()1133 Npc_chooser::~Npc_chooser(
1134 ) {
1135 	gtk_widget_destroy(get_widget());
1136 }
1137 
1138 /*
1139  *  Unselect.
1140  */
1141 
unselect(bool need_render)1142 void Npc_chooser::unselect(
1143     bool need_render            // 1 to render and show.
1144 ) {
1145 	if (selected >= 0) {
1146 		selected = -1;
1147 		if (need_render) {
1148 			render();
1149 		}
1150 		if (sel_changed)    // Tell client.
1151 			(*sel_changed)();
1152 	}
1153 	update_statusbar();
1154 }
1155 
1156 /*
1157  *  Show selection or range in window.
1158  */
1159 
update_statusbar()1160 void Npc_chooser::update_statusbar(
1161 ) {
1162 	char buf[150];
1163 	if (status_id >= 0)     // Remove prev. selection msg.
1164 		gtk_statusbar_remove(GTK_STATUSBAR(sbar), sbar_sel, status_id);
1165 	if (selected >= 0) {
1166 		int npcnum = info[selected].npcnum;
1167 		Estudio_npc &npc = get_npcs()[npcnum];
1168 		g_snprintf(buf, sizeof(buf), "Npc %d:  '%s'%s",
1169 		           npcnum, npc.name.c_str(),
1170 		           npc.unused ? " (unused)" : "");
1171 		status_id = gtk_statusbar_push(GTK_STATUSBAR(sbar),
1172 		                               sbar_sel, buf);
1173 	} else if (!info.empty() && !group) {
1174 		g_snprintf(buf, sizeof(buf), "NPCs %d to %d",
1175 		           info[rows[row0].index0].npcnum, last_npc);
1176 		status_id = gtk_statusbar_push(GTK_STATUSBAR(sbar),
1177 		                               sbar_sel, buf);
1178 	} else
1179 		status_id = -1;
1180 }
1181 
1182 
1183