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