1 /**
2  ** A GTK widget showing a palette's colors.
3  **
4  ** Written: 12/24/2000 - JSF
5  **/
6 
7 /*
8 Copyright (C) 2000-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 "paledit.h"
30 
31 #include "shapefile.h"
32 #include "u7drag.h"
33 #include "utils.h"
34 
35 #include <glib.h>
36 
37 #include <cctype>
38 #include <cstdio>
39 #include <cstring>
40 #include <iomanip>
41 #include <iostream>
42 
43 using EStudio::Alert;
44 using EStudio::Prompt;
45 using std::cout;
46 using std::endl;
47 using std::ifstream;
48 using std::make_unique;
49 using std::ofstream;
50 using std::ostream;
51 using std::setw;
52 using std::string;
53 
54 /*
55  *  Write out a single palette to a buffer.
56  */
57 
Write_palette(unsigned char * buf,ExultRgbCmap * pal)58 static void Write_palette(
59     unsigned char *buf,     // 3*256 bytes.
60     ExultRgbCmap *pal       // Palette to write.
61 ) {
62 	for (int i = 0; i < 256; i++) {
63 		int r = (pal->colors[i] >> 16) & 255;
64 		int g = (pal->colors[i] >> 8) & 255;
65 		int b = pal->colors[i] & 255;
66 		buf[3 * i] = r / 4; // Range 0-63.
67 		buf[3 * i + 1] = g / 4;
68 		buf[3 * i + 2] = b / 4;
69 	}
70 }
71 
72 /*
73  *  Blit onto screen.
74  */
75 
show(int x,int y,int w,int h)76 inline void Palette_edit::show(
77     int x, int y, int w, int h  // Area to blit.
78 ) {
79 	GtkAllocation alloc = {0, 0, 0, 0};
80 	gtk_widget_get_allocation(draw, &alloc);
81 	if (drawgc != nullptr) {   // In expose Callback
82 		int bw = alloc.width  / 16;
83 		int bh = alloc.height / 16;
84 		int rw = alloc.width  % 16;
85 		int rh = alloc.height % 16;
86 		// Two segments intersect iff neither is entirely right of the other
87 		for (int ly = 0; ly < 16; ly++) {
88 			int yp = (ly * bh) + (ly <= rh ? ly : rh);
89 			int hp = bh + (ly <= rh ? 1 : 0);
90 			if ((yp <= (y + h)) && (y <= (yp + hp))) {
91 				for (int lx = 0; lx < 16; lx++) {
92 					int xp = (lx * bw) + (lx <= rw ? lx : rw);
93 					int wp = bw + (lx <= rw ? 1 : 0);
94 					if ((xp <= (x + w)) && (x <= (xp + wp))) {
95 						guint32 color = palettes[cur_pal]->colors[lx + ly * 16];
96 						cairo_set_source_rgb(drawgc,
97 						                     ((color >> 16) & 255) / 255.0,
98 						                     ((color >> 8) & 255) / 255.0,
99 						                     (color & 255) / 255.0);
100 						cairo_rectangle(drawgc, xp, yp, wp, hp);
101 						cairo_fill(drawgc);
102 					}
103 				}
104 			}
105 		}
106 	}
107 	if ((selected >= 0) && (drawgc != nullptr)) {   // Show selected.
108 		// Draw yellow box.
109 		cairo_set_line_width(drawgc, 1.0);
110 		cairo_set_source_rgb(drawgc,
111 		                     ((drawfg >> 16) & 255) / 255.0,
112 		                     ((drawfg >> 8) & 255) / 255.0,
113 		                     (drawfg & 255) / 255.0);
114 		cairo_rectangle(drawgc, selected_box.x, selected_box.y, selected_box.w, selected_box.h);
115 		cairo_stroke(drawgc);
116 	}
117 }
118 
119 /*
120  *  Select an entry.  This should be called after rendering
121  *  the shape.
122  */
123 
select(int new_sel)124 void Palette_edit::select(
125     int new_sel
126 ) {
127 	selected = new_sel;
128 	// Remove prev. selection msg.
129 	gtk_statusbar_pop(GTK_STATUSBAR(sbar), sbar_sel);
130 	char buf[150];          // Show new selection.
131 	g_snprintf(buf, sizeof(buf), "Color %d (0x%02x)", new_sel, new_sel);
132 	gtk_statusbar_push(GTK_STATUSBAR(sbar), sbar_sel, buf);
133 }
134 
135 /*
136  *  Load/reload from file.
137  */
138 
load_internal()139 void Palette_edit::load_internal(
140 ) {
141 	// Free old.
142 	for (auto *palette : palettes)
143 		delete palette;
144 	unsigned cnt = flex_info->size();
145 	palettes.resize(cnt);       // Set size of list.
146 	if (!cnt)           // No palettes?
147 		new_palette();      // Create 1 blank palette.
148 	else {
149 		for (unsigned pnum = 0; pnum < cnt; pnum++) {
150 			size_t len;
151 			unsigned char *buf = flex_info->get(pnum, len);
152 			palettes[pnum] = new ExultRgbCmap;
153 			for (size_t i = 0; i < len / 3; i++)
154 				palettes[pnum]->colors[i] = (buf[3 * i] << 16) * 4 +
155 				            (buf[3 * i + 1] << 8) * 4 + buf[3 * i + 2] * 4;
156 			for (size_t i = len / 3; i < 256; i++)
157 				palettes[pnum]->colors[i] = 0;
158 		}
159 	}
160 }
161 
162 /*
163  *  Draw the palette.  This need only be called when it changes.
164  */
165 
render()166 void Palette_edit::render(
167 ) {
168 	GtkAllocation alloc = {0, 0, 0, 0};
169 	gtk_widget_get_allocation(draw, &alloc);
170 	int neww = alloc.width;
171 	int newh = alloc.height;
172 	// Changed size?
173 	if (neww != width || newh != height) {
174 		width = neww;
175 		height = newh;
176 	}
177 	// Figure cell size.
178 	int eachw = width / 16;
179 	int eachh = height / 16;
180 	// Figure extra pixels.
181 	int extraw = width % 16;
182 	int extrah = height % 16;
183 	if (selected >= 0) {    // Update selected box.
184 		int selx = selected % 16;
185 		int sely = selected / 16;
186 		selected_box.x = selx * eachw;
187 		selected_box.y = sely * eachh;
188 		selected_box.w = eachw;
189 		selected_box.h = eachh;
190 		if (selx < extraw) { // Watch for extra pixels.
191 			selected_box.w++;
192 			selected_box.x += selx;
193 		} else
194 			selected_box.x += extraw;
195 		if (sely < extrah) {
196 			selected_box.h++;
197 			selected_box.y += sely;
198 		} else
199 			selected_box.y += extrah;
200 		select(selected);
201 	}
202 	gtk_widget_queue_draw(draw);
203 }
204 
205 /*
206  *  Handle double-click on a color by bringing up a color-selector.
207  */
208 
double_clicked()209 void Palette_edit::double_clicked(
210 ) {
211 	cout << "Double-clicked" << endl;
212 	if (selected < 0 || colorsel)   // Only one at a time.
213 		return;         // Nothing selected.
214 	char buf[150];          // Show new selection.
215 	g_snprintf(buf, sizeof(buf), "Color %d (0x%02x)", selected, selected);
216 	colorsel = GTK_COLOR_CHOOSER_DIALOG(
217 	               gtk_color_chooser_dialog_new(buf, nullptr));
218 	// Get color.
219 	guint32 c = palettes[cur_pal]->colors[selected];
220 	GdkRGBA  rgba;
221 	rgba.red   = ((c >> 16) & 0xff) / 255.0;
222 	rgba.green = ((c >>  8) & 0xff) / 255.0;
223 	rgba.blue  = ((c >>  0) & 0xff) / 255.0;
224 	rgba.alpha = 1.0;
225 	gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorsel), &rgba);
226 	if (gtk_dialog_run(GTK_DIALOG(colorsel)) == GTK_RESPONSE_OK) {
227 		gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorsel), &rgba);
228 		auto r = static_cast<unsigned char>(rgba.red   * 255.0);
229 		auto g = static_cast<unsigned char>(rgba.green * 255.0);
230 		auto b = static_cast<unsigned char>(rgba.blue  * 255.0);
231 		if (selected >= 0)
232 			palettes[cur_pal]->colors[selected] =
233 			    (r << 16) + (g << 8) + b;
234 		// Send to flex file.
235 		update_flex(cur_pal);
236 		render();
237 	}
238 	gtk_widget_destroy(GTK_WIDGET(colorsel));
239 	colorsel = nullptr;
240 }
241 
242 /*
243  *  Configure the viewing window.
244  */
245 
configure(GtkWidget * widget,GdkEventConfigure * event,gpointer data)246 gint Palette_edit::configure(
247     GtkWidget *widget,      // The view window.
248     GdkEventConfigure *event,
249     gpointer data           // ->Palette_edit
250 ) {
251 	ignore_unused_variable_warning(event, widget);
252 	auto *paled = static_cast<Palette_edit *>(data);
253 	if (!paled->width) {    // First time?
254 		// Foreground = yellow.
255 		paled->drawfg = (255 << 16) + (255 << 8);
256 	}
257 	paled->render();
258 	return TRUE;
259 }
260 
261 /*
262  *  Handle an expose event.
263  */
264 
expose(GtkWidget * widget,cairo_t * cairo,gpointer data)265 gint Palette_edit::expose(
266     GtkWidget *widget,      // The view window.
267     cairo_t *cairo,
268     gpointer data           // ->Palette_edit.
269 ) {
270 	ignore_unused_variable_warning(widget);
271 	auto *paled = static_cast<Palette_edit *>(data);
272 	paled->set_graphic_context(cairo);
273 	GdkRectangle area = { 0, 0, 0, 0 };
274 	gdk_cairo_get_clip_rectangle(cairo, &area);
275 	paled->show(area.x, area.y, area.width, area.height);
276 	paled->set_graphic_context(nullptr);
277 	return TRUE;
278 }
279 
280 /*
281  *  Handle a mouse button press event.
282  */
283 
mouse_press(GtkWidget * widget,GdkEventButton * event,gpointer data)284 gint Palette_edit::mouse_press(
285     GtkWidget *widget,      // The view window.
286     GdkEventButton *event,
287     gpointer data           // ->Palette_edit.
288 ) {
289 	ignore_unused_variable_warning(widget);
290 	auto *paled = static_cast<Palette_edit *>(data);
291 
292 	if (event->button == 4 || event->button == 5) // mouse wheel
293 		return TRUE;
294 
295 	if (paled->colorsel)
296 		return TRUE;      // Already editing a color.
297 	int old_selected = paled->selected;
298 	int width = paled->width;
299 	int height = paled->height;
300 	int eventx = static_cast<int>(event->x);
301 	int eventy = static_cast<int>(event->y);
302 	// Figure cell size.
303 	int eachw = width / 16;
304 	int eachh = height / 16;
305 	// Figure extra pixels.
306 	int extraw = width % 16;
307 	int extrah = height % 16;
308 	int extrax = extraw * (eachw + 1); // Total length of extra-sized boxes.
309 	int extray = extrah * (eachh + 1);
310 	int selx;
311 	int sely;         // Gets box indices.
312 	if (eventx < extrax)
313 		selx = eventx / (eachw + 1);
314 	else
315 		selx = extraw + (eventx - extrax) / eachw;
316 	if (eventy < extray)
317 		sely = eventy / (eachh + 1);
318 	else
319 		sely = extrah + (eventy - extray) / eachh;
320 	paled->selected = sely * 16 + selx;
321 	if (paled->selected == old_selected) {
322 		// Same square.  Check for dbl-click.
323 		if (reinterpret_cast<GdkEvent *>(event)->type == GDK_2BUTTON_PRESS)
324 			paled->double_clicked();
325 	} else {
326 		paled->render();
327 	}
328 	if (event->button == 3)
329 		gtk_menu_popup_at_pointer(GTK_MENU(paled->create_popup()),
330 		                          reinterpret_cast<GdkEvent *>(event));
331 	return TRUE;
332 }
333 
334 /*
335  *  Someone wants the dragged shape.
336  */
337 
drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * seldata,guint info,guint time,gpointer data)338 void Palette_edit::drag_data_get(
339     GtkWidget *widget,      // The view window.
340     GdkDragContext *context,
341     GtkSelectionData *seldata,  // Fill this in.
342     guint info,
343     guint time,
344     gpointer data           // ->Palette_edit.
345 ) {
346 	ignore_unused_variable_warning(widget, context, seldata, info, time, data);
347 	cout << "In DRAG_DATA_GET" << endl;
348 }
349 
350 /*
351  *  Beginning of a drag.
352  */
353 
drag_begin(GtkWidget * widget,GdkDragContext * context,gpointer data)354 gint Palette_edit::drag_begin(
355     GtkWidget *widget,      // The view window.
356     GdkDragContext *context,
357     gpointer data           // ->Palette_edit.
358 ) {
359 	ignore_unused_variable_warning(widget, context, data);
360 	cout << "In DRAG_BEGIN of Palette" << endl;
361 	//Palette_edit *paled = static_cast<Palette_edit *>(data);
362 	// Maybe someday.
363 	return TRUE;
364 }
365 
366 /*
367  *  Handle a change to the 'Palette #' spin button.
368  */
369 
palnum_changed(GtkAdjustment * adj,gpointer data)370 void Palette_edit::palnum_changed(
371     GtkAdjustment *adj,     // The adjustment.
372     gpointer data           // ->Shape_chooser.
373 ) {
374 	auto *paled = static_cast<Palette_edit *>(data);
375 	gint newnum = static_cast<gint>(gtk_adjustment_get_value(adj));
376 	paled->show_palette(newnum);
377 	paled->render();
378 	paled->enable_controls();
379 }
380 
381 /*
382  *  Callbacks for buttons:
383  */
384 void
on_exportbtn_clicked(GtkButton * button,gpointer user_data)385 on_exportbtn_clicked(GtkButton       *button,
386                      gpointer         user_data) {
387 	ignore_unused_variable_warning(button);
388 	Create_file_selection("Export palette to text format",
389 	                      "<PATCH>", nullptr,
390 	                      {},
391 	                      GTK_FILE_CHOOSER_ACTION_SAVE,
392 	                      Palette_edit::export_palette,
393 	                      user_data);
394 }
395 
396 void
on_importbtn_clicked(GtkButton * button,gpointer user_data)397 on_importbtn_clicked(GtkButton       *button,
398                      gpointer         user_data) {
399 	ignore_unused_variable_warning(button);
400 	Create_file_selection("Import palette from text format",
401 	                      "<STATIC>", nullptr,
402 	                      {},
403 	                      GTK_FILE_CHOOSER_ACTION_OPEN,
404 	                      Palette_edit::import_palette,
405 	                      user_data);
406 }
407 
408 void
on_insert_btn_clicked(GtkButton * button,gpointer user_data)409 on_insert_btn_clicked(GtkButton       *button,
410                       gpointer         user_data) {
411 	ignore_unused_variable_warning(button);
412 	auto *paled = static_cast<Palette_edit *>(user_data);
413 	paled->add_palette();
414 }
415 void
on_remove_btn_clicked(GtkButton * button,gpointer user_data)416 on_remove_btn_clicked(GtkButton       *button,
417                       gpointer         user_data) {
418 	ignore_unused_variable_warning(button);
419 	auto *paled = static_cast<Palette_edit *>(user_data);
420 	paled->remove_palette();
421 }
422 void
on_up_btn_clicked(GtkButton * button,gpointer user_data)423 on_up_btn_clicked(GtkButton       *button,
424                   gpointer         user_data) {
425 	ignore_unused_variable_warning(button);
426 	auto *paled = static_cast<Palette_edit *>(user_data);
427 	paled->move_palette(true);
428 }
429 void
on_down_btn_clicked(GtkButton * button,gpointer user_data)430 on_down_btn_clicked(GtkButton       *button,
431                     gpointer         user_data) {
432 	ignore_unused_variable_warning(button);
433 	auto *paled = static_cast<Palette_edit *>(user_data);
434 	paled->move_palette(false);
435 }
436 
437 /*
438  *  Create box with 'Palette #', 'Import', 'Move' controls.
439  */
440 
create_controls()441 GtkWidget *Palette_edit::create_controls(
442 ) {
443 	// Create main box.
444 	GtkWidget *topframe = gtk_frame_new(nullptr);
445 	widget_set_margins(topframe, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
446 	gtk_widget_show(topframe);
447 
448 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
449 	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
450 	gtk_widget_show(vbox);
451 	gtk_container_add(GTK_CONTAINER(topframe), vbox);
452 
453 	GtkWidget *hbox0 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
454 	gtk_box_set_homogeneous(GTK_BOX(hbox0), FALSE);
455 	widget_set_margins(hbox0, 1*HMARGIN, 1*HMARGIN, 1*VMARGIN, 1*VMARGIN);
456 	gtk_widget_show(hbox0);
457 	gtk_box_pack_start(GTK_BOX(vbox), hbox0, TRUE, TRUE, 0);
458 	/*
459 	 *  The 'Edit' controls.
460 	 */
461 	GtkWidget *frame = gtk_frame_new("Edit");
462 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
463 	gtk_widget_show(frame);
464 	gtk_box_pack_start(GTK_BOX(hbox0), frame, FALSE, FALSE, 0);
465 
466 	GtkWidget *hbuttonbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
467 	gtk_widget_show(hbuttonbox);
468 	gtk_container_add(GTK_CONTAINER(frame), hbuttonbox);
469 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbuttonbox),
470 	                          GTK_BUTTONBOX_START);
471 	gtk_box_set_spacing(GTK_BOX(hbuttonbox), 0);
472 
473 	insert_btn = gtk_button_new_with_label("New");
474 	widget_set_margins(insert_btn, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
475 	gtk_widget_show(insert_btn);
476 	gtk_container_add(GTK_CONTAINER(hbuttonbox), insert_btn);
477 	gtk_widget_set_can_default(GTK_WIDGET(insert_btn), TRUE);
478 
479 	remove_btn = gtk_button_new_with_label("Remove");
480 	widget_set_margins(remove_btn, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
481 	gtk_widget_show(remove_btn);
482 	gtk_container_add(GTK_CONTAINER(hbuttonbox), remove_btn);
483 	gtk_widget_set_can_default(GTK_WIDGET(remove_btn), TRUE);
484 
485 	g_signal_connect(G_OBJECT(insert_btn), "clicked",
486 	                 G_CALLBACK(on_insert_btn_clicked),
487 	                 this);
488 	g_signal_connect(G_OBJECT(remove_btn), "clicked",
489 	                 G_CALLBACK(on_remove_btn_clicked),
490 	                 this);
491 	/*
492 	 *  The 'Move' controls.
493 	 */
494 	frame = gtk_frame_new("Move");
495 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
496 	gtk_widget_show(frame);
497 	gtk_box_pack_start(GTK_BOX(hbox0), frame, FALSE, FALSE, 0);
498 
499 	GtkWidget *bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
500 	gtk_box_set_homogeneous(GTK_BOX(bbox), TRUE);
501 	gtk_widget_show(bbox);
502 	gtk_container_add(GTK_CONTAINER(frame), bbox);
503 
504 	down_btn = gtk_button_new();
505 	widget_set_margins(down_btn, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
506 	gtk_widget_show(down_btn);
507 	gtk_box_pack_start(GTK_BOX(bbox), down_btn, FALSE, FALSE, 0);
508 	gtk_widget_set_can_default(GTK_WIDGET(down_btn), TRUE);
509 	GtkWidget *arrow = gtk_image_new_from_icon_name("go-down", GTK_ICON_SIZE_BUTTON);
510 	gtk_button_set_image(GTK_BUTTON(down_btn), arrow);
511 
512 	up_btn = gtk_button_new();
513 	widget_set_margins(up_btn, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
514 	gtk_widget_show(up_btn);
515 	gtk_box_pack_start(GTK_BOX(bbox), up_btn, FALSE, FALSE, 0);
516 	gtk_widget_set_can_default(GTK_WIDGET(up_btn), TRUE);
517 	arrow = gtk_image_new_from_icon_name("go-up", GTK_ICON_SIZE_BUTTON);
518 	gtk_button_set_image(GTK_BUTTON(up_btn), arrow);
519 
520 	g_signal_connect(G_OBJECT(down_btn), "clicked",
521 	                 G_CALLBACK(on_down_btn_clicked),
522 	                 this);
523 	g_signal_connect(G_OBJECT(up_btn), "clicked",
524 	                 G_CALLBACK(on_up_btn_clicked),
525 	                 this);
526 	/*
527 	 *  The 'File' controls.
528 	 */
529 	frame = gtk_frame_new("File");
530 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
531 	gtk_widget_show(frame);
532 	gtk_box_pack_start(GTK_BOX(hbox0), frame, FALSE, FALSE, 0);
533 
534 	hbuttonbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
535 	gtk_widget_show(hbuttonbox);
536 	gtk_container_add(GTK_CONTAINER(frame), hbuttonbox);
537 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbuttonbox),
538 	                          GTK_BUTTONBOX_START);
539 	gtk_box_set_spacing(GTK_BOX(hbuttonbox), 0);
540 
541 	GtkWidget *importbtn = gtk_button_new_with_label("Import");
542 	widget_set_margins(importbtn, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
543 	gtk_widget_show(importbtn);
544 	gtk_container_add(GTK_CONTAINER(hbuttonbox), importbtn);
545 	gtk_widget_set_can_default(GTK_WIDGET(importbtn), TRUE);
546 
547 	GtkWidget *exportbtn = gtk_button_new_with_label("Export");
548 	widget_set_margins(exportbtn, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
549 	gtk_widget_show(exportbtn);
550 	gtk_container_add(GTK_CONTAINER(hbuttonbox), exportbtn);
551 	gtk_widget_set_can_default(GTK_WIDGET(exportbtn), TRUE);
552 
553 	g_signal_connect(G_OBJECT(importbtn), "clicked",
554 	                 G_CALLBACK(on_importbtn_clicked),
555 	                 this);
556 	g_signal_connect(G_OBJECT(exportbtn), "clicked",
557 	                 G_CALLBACK(on_exportbtn_clicked),
558 	                 this);
559 	return topframe;
560 }
561 
562 /*
563  *  Enable/disable controls after changes.
564  */
565 
enable_controls()566 void Palette_edit::enable_controls(
567 ) {
568 	// Can't delete last one.
569 	gtk_widget_set_sensitive(remove_btn, cur_pal >= 0 &&
570 	                         palettes.size() > 1);
571 	if (cur_pal == -1) {    // No palette?
572 		gtk_widget_set_sensitive(down_btn, false);
573 		gtk_widget_set_sensitive(up_btn, false);
574 		gtk_widget_set_sensitive(remove_btn, false);
575 	} else {
576 		gtk_widget_set_sensitive(down_btn,
577 		                         static_cast<unsigned>(cur_pal) < palettes.size() - 1);
578 		gtk_widget_set_sensitive(up_btn, cur_pal > 0);
579 		gtk_widget_set_sensitive(remove_btn, palettes.size() > 1);
580 	}
581 }
582 
583 /*
584  *  Set up box.
585  */
586 
setup()587 void Palette_edit::setup(
588 ) {
589 	// Put things in a vert. box.
590 	GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
591 	gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
592 	gtk_widget_show(vbox);
593 	set_widget(vbox);
594 
595 	// A frame looks nice.
596 	GtkWidget *frame = gtk_frame_new(nullptr);
597 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
598 	widget_set_margins(frame, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
599 	gtk_widget_show(frame);
600 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
601 
602 	draw = gtk_drawing_area_new();  // Create drawing area window.
603 //	gtk_widget_set_size_request(draw, w, h);
604 	// Indicate the events we want.
605 	gtk_widget_set_events(draw, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
606 	                      | GDK_BUTTON1_MOTION_MASK);
607 	// Set "configure" handler.
608 	g_signal_connect(G_OBJECT(draw), "configure-event",
609 	                 G_CALLBACK(configure), this);
610 	// Set "expose-event" - "draw" handler.
611 	g_signal_connect(G_OBJECT(draw), "draw",
612 	                 G_CALLBACK(expose), this);
613 	// Set mouse click handler.
614 	g_signal_connect(G_OBJECT(draw), "button-press-event",
615 	                 G_CALLBACK(mouse_press), this);
616 	// Mouse motion.
617 	g_signal_connect(G_OBJECT(draw), "drag-begin",
618 	                 G_CALLBACK(drag_begin), this);
619 	g_signal_connect(G_OBJECT(draw), "drag-data-get",
620 	                 G_CALLBACK(drag_data_get), this);
621 	gtk_container_add(GTK_CONTAINER(frame), draw);
622 	widget_set_margins(draw, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
623 	gtk_widget_show(draw);
624 
625 	// At bottom, a status bar.
626 	sbar = gtk_statusbar_new();
627 	sbar_sel = gtk_statusbar_get_context_id(GTK_STATUSBAR(sbar),
628 	                                        "selection");
629 	// At the bottom, status bar & frame:
630 	GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
631 	gtk_box_set_homogeneous(GTK_BOX(hbox1), FALSE);
632 	gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
633 	gtk_widget_show(hbox1);
634 	gtk_box_pack_start(GTK_BOX(hbox1), sbar, TRUE, TRUE, 0);
635 	// Palette # to right of sbar.
636 	GtkWidget *label = gtk_label_new("Palette #:");
637 	widget_set_margins(label, 2*HMARGIN, 1*HMARGIN, 2*VMARGIN, 2*VMARGIN);
638 	gtk_box_pack_start(GTK_BOX(hbox1), label, FALSE, FALSE, 0);
639 	gtk_widget_show(label);
640 
641 	// A spin button for palette#.
642 	palnum_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0,
643 	                            palettes.size() - 1, 1,
644 	                            2, 2));
645 	pspin = gtk_spin_button_new(palnum_adj, 1, 0);
646 	g_signal_connect(G_OBJECT(palnum_adj), "value-changed",
647 	                 G_CALLBACK(palnum_changed), this);
648 	widget_set_margins(pspin, 1*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
649 	gtk_box_pack_start(GTK_BOX(hbox1), pspin, FALSE, FALSE, 0);
650 	gtk_widget_show(pspin);
651 
652 	// Add edit controls to bottom.
653 	gtk_box_pack_start(GTK_BOX(vbox), create_controls(), FALSE, FALSE, 0);
654 	widget_set_margins(sbar, 2*HMARGIN, 2*HMARGIN, 2*VMARGIN, 2*VMARGIN);
655 	gtk_widget_show(sbar);
656 	enable_controls();
657 }
658 
659 /*
660  *  Create/add a new palette.
661  */
662 
new_palette()663 void Palette_edit::new_palette(
664 ) {
665 	auto *newpal = new ExultRgbCmap;        // R, G, B, then all black.
666 	memset(&newpal->colors[0], 0, 256 * sizeof(guint32));
667 	newpal->colors[0] = 255 << 16;
668 	newpal->colors[1] = 255 << 8;
669 	newpal->colors[2] = 255;
670 	int index = palettes.size();    // Index of new palette.
671 	palettes.push_back(newpal);
672 	update_flex(index);     // Add to file.
673 }
674 
675 /*
676  *  Update palette entry in flex file.
677  */
678 
update_flex(int pnum)679 void Palette_edit::update_flex(
680     int pnum            // Palette # to send to file.
681 ) {
682 	auto buf = make_unique<unsigned char[]>(3 * 256);
683 	Write_palette(buf.get(), palettes[pnum]);
684 	// Update or append file data.
685 	flex_info->set(pnum, std::move(buf), 3 * 256);
686 	flex_info->set_modified();
687 }
688 
689 /*
690  *  Create the list for a single palette.
691  */
692 
Palette_edit(Flex_file_info * flinfo)693 Palette_edit::Palette_edit(
694     Flex_file_info *flinfo      // Flex-file info.
695 ) : Object_browser(nullptr, flinfo),
696 	flex_info(flinfo), /* image(nullptr),*/ width(0), height(0),
697 	cur_pal(0), colorsel(nullptr) {
698 	load_internal();             // Load from file.
699 	setup();
700 }
701 
702 /*
703  *  Delete.
704  */
705 
~Palette_edit()706 Palette_edit::~Palette_edit(
707 ) {
708 	for (auto *palette : palettes)
709 		delete palette;
710 	gtk_widget_destroy(get_widget());
711 }
712 
713 /*
714  *  Get i'th palette.
715  */
716 
show_palette(int palnum)717 void Palette_edit::show_palette(
718     int palnum
719 ) {
720 	cur_pal = palnum;
721 }
722 
723 /*
724  *  Unselect.
725  */
726 
unselect(bool need_render)727 void Palette_edit::unselect(
728     bool need_render            // 1 to render and show.
729 ) {
730 	if (selected >= 0) {
731 		selected = -1;
732 		if (need_render) {
733 			render();
734 		}
735 	}
736 }
737 
738 /*
739  *  Move a palette within the list.
740  */
741 
move_palette(bool up)742 void Palette_edit::move_palette(
743     bool up
744 ) {
745 	if (cur_pal < 0)
746 		return;
747 	ExultRgbCmap *tmp;
748 	if (up) {
749 		if (cur_pal > 0) {
750 			tmp = palettes[cur_pal - 1];
751 			palettes[cur_pal - 1] = palettes[cur_pal];
752 			palettes[cur_pal] = tmp;
753 			cur_pal--;
754 			flex_info->swap(cur_pal);// Update flex-file list.
755 		}
756 	} else {
757 		if (static_cast<unsigned>(cur_pal) < palettes.size() - 1) {
758 			tmp = palettes[cur_pal + 1];
759 			palettes[cur_pal + 1] = palettes[cur_pal];
760 			palettes[cur_pal] = tmp;
761 			flex_info->swap(cur_pal);// Update flex-file list.
762 			cur_pal++;
763 		}
764 	}
765 	flex_info->set_modified();
766 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(pspin), cur_pal);
767 }
768 
769 /*
770  *  Update upper bound of a range widget.
771  */
772 
Update_range_upper(GtkAdjustment * adj,int new_upper)773 void Update_range_upper(
774     GtkAdjustment *adj,
775     int new_upper
776 ) {
777 	gtk_adjustment_set_upper(adj, new_upper);
778 	g_signal_emit_by_name(G_OBJECT(adj), "changed");
779 }
780 
781 /*
782  *  Add a new palette at the end of the list.
783  */
784 
add_palette()785 void Palette_edit::add_palette(
786 ) {
787 	new_palette();
788 	cur_pal = palettes.size() - 1;  // Set to display new palette.
789 	Update_range_upper(palnum_adj, palettes.size() - 1);
790 	// This will update the display:
791 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(pspin), cur_pal);
792 }
793 
794 /*
795  *  Remove the current palette.
796  */
797 
remove_palette()798 void Palette_edit::remove_palette(
799 ) {
800 	// Don't delete the last one.
801 	if (cur_pal < 0 || palettes.size() < 2)
802 		return;
803 	if (Prompt(
804 	            "Do you really want to delete the palette you're viewing?",
805 	            "Yes", "No") != 0)
806 		return;
807 	delete palettes[cur_pal];
808 	palettes.erase(palettes.begin() + cur_pal);
809 	flex_info->remove(cur_pal);
810 	flex_info->set_modified();
811 	if (static_cast<unsigned>(cur_pal) >= palettes.size())
812 		cur_pal = palettes.size() - 1;
813 	Update_range_upper(palnum_adj, palettes.size() - 1);
814 	// This will update the display:
815 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(pspin), cur_pal);
816 	render();           // Cur_pal may not have changed.
817 }
818 
819 /*
820  *  Export current palette.
821  */
822 
export_palette(const char * fname,gpointer user_data)823 void Palette_edit::export_palette(
824     const char *fname,
825     gpointer user_data
826 ) {
827 	auto *paled = static_cast<Palette_edit *>(user_data);
828 	if (U7exists(fname)) {
829 		char *msg = g_strdup_printf(
830 		                "'%s' already exists.  Overwrite?", fname);
831 		int answer = Prompt(msg, "Yes", "No");
832 		g_free(msg);
833 		if (answer != 0)
834 			return;
835 	}
836 	// Write out current palette.
837 	ExultRgbCmap *pal = paled->palettes[paled->cur_pal];
838 	ofstream out(fname);        // OKAY that it's a 'text' file.
839 	out << "GIMP Palette" << endl;
840 	out << "# Exported from ExultStudio" << endl;
841 	int i;              // Skip 0's at end.
842 	for (i = 255; i > 0; i--)
843 		if (pal->colors[i] != 0)
844 			break;
845 	int last_color = i;
846 	for (i = 0; i <= last_color; i++) {
847 		int r = (pal->colors[i] >> 16) & 255;
848 		int g = (pal->colors[i] >> 8) & 255;
849 		int b = pal->colors[i] & 255;
850 		out << setw(3) << r << ' ' << setw(3) << g << ' ' <<
851 		    setw(3) << b << endl;
852 	}
853 	out.close();
854 }
855 
856 /*
857  *  Import current palette.
858  */
859 
import_palette(const char * fname,gpointer user_data)860 void Palette_edit::import_palette(
861     const char *fname,
862     gpointer user_data
863 ) {
864 	auto *paled = static_cast<Palette_edit *>(user_data);
865 	char *msg = g_strdup_printf(
866 	                "Overwrite current palette from '%s'?", fname);
867 	int answer = Prompt(msg, "Yes", "No");
868 	g_free(msg);
869 	if (answer != 0)
870 		return;
871 	// Read in current palette.
872 	ExultRgbCmap *pal = paled->palettes[paled->cur_pal];
873 	ifstream in(fname);     // OKAY that it's a 'text' file.
874 	char buf[256];
875 	in.getline(buf, sizeof(buf));   // Skip 1st line.
876 	if (!in.good()) {
877 		Alert("Error reading '%s'", fname);
878 		return;
879 	}
880 	int i = 0;          // Color #.
881 	while (i < 256 && !in.eof()) {
882 		in.getline(buf, sizeof(buf));
883 		char *ptr = &buf[0];
884 		// Skip spaces.
885 		while (ptr < buf + sizeof(buf) && *ptr && isspace(static_cast<unsigned char>(*ptr)))
886 			ptr++;
887 		if (*ptr == '#')
888 			continue;   // Comment.
889 		int r;
890 		int g;
891 		int b;
892 		if (sscanf(buf, "%d %d %d", &r, &g, &b) == 3)
893 			pal->colors[i++] = (r << 16) + (g << 8) + b;
894 	}
895 	in.close();
896 	// Add to file.
897 	paled->update_flex(paled->cur_pal);
898 	paled->render();
899 }
900 
901