20130427
[gdash.git] / src / editor / editorwidgets.cpp
blob5945251bc1d948f8d05426e2985f4291b524fde7
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
29 #include "editor/editorcellrenderer.hpp"
30 #include "editor/editorwidgets.hpp"
31 #include "editor/editor.hpp"
32 #include "gtk/gtkui.hpp"
33 #include "cave/elementproperties.hpp"
37 * A COMBO BOX with c64 colors.
38 * use color_combo_new for creating and color_combo_get_color for getting color in gdash format.
41 /* this data field always stores the previously selected color. */
42 /* it is needed when a select atari or select rgb dialog is escaped, and we must set the original color. */
43 /* the combo box itself cannot store it, as is is already set to the select atari... or select rgb... line. */
44 #define GDASH_COLOR "gdash-color"
45 #define GDASH_COLOR_INDEX "gdash-color-index"
47 static GtkTreePath *selected_color_path = NULL;
49 enum {
50 COL_COLOR_ACTION, /* some lines selected will trigger showing a dialog */
51 COL_COLOR_NAME, /* name of color (eg. c64 black), or action (select rgb color) */
52 COL_COLOR_PIXBUF, /* pixbuf to be shown (used for c64 colors, and currently selected color) */
53 COL_COLOR_C64_INDEX, /* used for c64 colors */
54 COL_COLOR_MAX
57 typedef enum {
58 COLOR_ACTION_NONE,
59 COLOR_ACTION_SELECT_C64,
60 COLOR_ACTION_SELECT_ATARI,
61 COLOR_ACTION_SELECT_DTV,
62 COLOR_ACTION_SELECT_RGB,
63 } ColorAction;
66 * creates a small pixbuf with the specified color
68 static GdkPixbuf *
69 color_combo_pixbuf_for_gd_color(const GdColor &col) {
70 int x, y;
71 guint32 pixel;
72 GdkPixbuf *pixbuf;
74 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &x, &y);
76 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, x, y);
77 unsigned char r, g, b;
78 col.get_rgb(r, g, b);
79 pixel = (guint32(r) << 24) + (guint32(g) << 16) + (guint32(b) << 8);
80 gdk_pixbuf_fill(pixbuf, pixel);
82 return pixbuf;
85 /* set to a color - first check if that is a c64 color.
86 if it is, simply ump to that item. if not, create
87 a special item and select that one. */
88 void
89 gd_color_combo_set(GtkComboBox *combo, const GdColor &color) {
90 GdColor *pcolor = static_cast<GdColor *>(g_object_get_data(G_OBJECT(combo), GDASH_COLOR));
92 *pcolor = color; /* set its own object to be a copy of the requested color */
94 if (color.is_c64()) {
95 GtkTreeIter iter;
97 char *path = g_strdup_printf("0:%d", color.get_c64_index());
98 gtk_tree_model_get_iter_from_string(gtk_combo_box_get_model(combo), &iter, path);
99 g_free(path);
100 gtk_combo_box_set_active_iter(combo, &iter);
101 } else {
102 GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
103 GtkTreeIter iter;
105 gtk_tree_model_get_iter(model, &iter, selected_color_path);
107 GdkPixbuf *pixbuf = color_combo_pixbuf_for_gd_color(color);
108 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_COLOR_PIXBUF, pixbuf, COL_COLOR_NAME, visible_name(color).c_str(), -1);
109 g_object_unref(pixbuf); /* now the tree store owns its own reference */
111 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter);
115 static gboolean
116 color_combo_drawing_area_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) {
117 GtkDialog *dialog = GTK_DIALOG(data);
119 gtk_dialog_response(dialog, GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), GDASH_COLOR_INDEX)));
121 return TRUE;
125 static void
126 color_combo_changed(GtkWidget *combo, gpointer data) {
127 GdColor *pcolor = static_cast<GdColor *>(g_object_get_data(G_OBJECT(combo), GDASH_COLOR));
128 GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
129 GtkTreeIter iter;
130 ColorAction action;
132 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
133 gtk_tree_model_get(model, &iter, COL_COLOR_ACTION, &action, -1);
134 switch (action) {
135 case COLOR_ACTION_SELECT_RGB: {
136 GtkWidget *dialog;
137 GtkColorSelection *colorsel;
138 gint response;
139 GdkColor gc;
140 GdColor prevcol;
142 prevcol = *pcolor; /* remember previous setting */
144 dialog = gtk_color_selection_dialog_new(_("Select Color"));
145 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gtk_widget_get_toplevel(combo)));
146 colorsel = GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
147 unsigned char r, g, b;
148 prevcol.get_rgb(r, g, b);
149 gc.red = r * 257; // yes, 257: 255*257=65535
150 gc.green = g * 257;
151 gc.blue = b * 257;
152 gtk_color_selection_set_previous_color(colorsel, &gc);
153 gtk_color_selection_set_current_color(colorsel, &gc);
154 gtk_color_selection_set_has_palette(colorsel, TRUE);
156 gtk_window_present_with_time(GTK_WINDOW(dialog), gtk_get_current_event_time());
157 response = gtk_dialog_run(GTK_DIALOG(dialog));
159 if (response == GTK_RESPONSE_OK) {
160 GdkColor gc;
162 gtk_color_selection_get_current_color(colorsel, &gc);
163 gd_color_combo_set(GTK_COMBO_BOX(combo), GdColor::from_rgb(gc.red >> 8, gc.green >> 8, gc.blue >> 8));
164 } else {
165 /* if not accepted, return button to original state */
166 gd_color_combo_set(GTK_COMBO_BOX(combo), prevcol);
169 gtk_widget_destroy(dialog);
171 break;
173 case COLOR_ACTION_SELECT_ATARI:
174 case COLOR_ACTION_SELECT_DTV: {
175 GtkWidget *dialog, *table, *frame;
176 GdColor prevcol;
177 int i;
178 int result;
179 GdColor(*colorfunc)(unsigned int i);
180 const char *title;
182 switch (action) {
183 case COLOR_ACTION_SELECT_ATARI:
184 // TRANSLATORS: Title text capitalization in English
185 title = _("Select Atari Color");
186 colorfunc = &GdColor::from_atari;
187 break;
188 case COLOR_ACTION_SELECT_DTV:
189 // TRANSLATORS: Title text capitalization in English
190 title = _("Select C64 DTV Color");
191 colorfunc = &GdColor::from_c64dtv;
192 break;
193 default:
194 g_assert_not_reached();
197 dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(gtk_widget_get_toplevel(combo)), GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
198 table = gtk_table_new(16, 16, TRUE);
199 for (i = 0; i < 256; i++) {
200 GdkColor color;
202 GtkWidget *da = gtk_drawing_area_new();
203 GdColor c = colorfunc(i);
204 g_object_set_data(G_OBJECT(da), GDASH_COLOR_INDEX, GUINT_TO_POINTER(i)); /* attach the color index as data */
205 gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK);
206 gtk_widget_set_tooltip_text(da, visible_name(c).c_str());
207 g_signal_connect(G_OBJECT(da), "button_press_event", G_CALLBACK(color_combo_drawing_area_button_press_event), dialog); /* mouse click */
208 unsigned char r, g, b;
209 c.get_rgb(r, g, b);
210 color.red = r * 256; /* 256 as gdk expect 16-bit/component */
211 color.green = g * 256;
212 color.blue = b * 256;
213 gtk_widget_modify_bg(da, GTK_STATE_NORMAL, &color);
214 gtk_widget_set_size_request(da, 16, 16);
215 gtk_table_attach_defaults(GTK_TABLE(table), da, i % 16, i % 16 + 1, i / 16, i / 16 + 1);
217 frame = gtk_frame_new(NULL);
218 gtk_container_set_border_width(GTK_CONTAINER(frame), 6);
219 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
220 gtk_container_add(GTK_CONTAINER(frame), table);
221 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame);
222 gtk_widget_show_all(GTK_DIALOG(dialog)->vbox);
224 prevcol = *pcolor;
225 result = gtk_dialog_run(GTK_DIALOG(dialog));
226 if (result >= 0)
227 gd_color_combo_set(GTK_COMBO_BOX(combo), colorfunc(result));
228 else
229 /* if not accepted, return button to original state */
230 gd_color_combo_set(GTK_COMBO_BOX(combo), prevcol);
232 gtk_widget_destroy(dialog);
234 break;
236 case COLOR_ACTION_SELECT_C64: {
237 int i;
239 gtk_tree_model_get(model, &iter, COL_COLOR_C64_INDEX, &i, -1);
240 *pcolor = GdColor::from_c64(i); /* set the stored color to the c64 color[i] */
242 break;
244 case COLOR_ACTION_NONE:
245 /* do nothing - this is the special color, eg. rgb. for that, we already updated pcolor and the like */
246 break;
251 /* we use a non-visible, non-selectable row for the currently selected color (unless it is a c64 color). */
252 static gboolean
253 color_combo_is_separator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
254 GtkTreePath *path;
255 gboolean result;
257 path = gtk_tree_model_get_path(model, iter);
258 result = !gtk_tree_path_compare(path, selected_color_path);
259 gtk_tree_path_free(path);
261 return result;
264 static void color_combo_set_sensitive(GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) {
265 g_object_set(cell, "sensitive", !gtk_tree_model_iter_has_child(tree_model, iter), NULL);
268 static void color_combo_destroyed(GtkWidget *combo, gpointer data) {
269 GdColor *pcolor = static_cast<GdColor *>(g_object_get_data(G_OBJECT(combo), GDASH_COLOR));
271 delete pcolor;
274 /* combo box creator. */
275 GtkWidget *gd_color_combo_new(const GdColor &initial) {
276 /* this is the base of the cave editor element combo box.
277 categories are autodetected by their integer values being >O_MAX */
278 GtkTreeStore *store;
279 GtkWidget *combo;
280 GtkCellRenderer *renderer;
281 GtkTreeIter iter, parent;
283 /* tree store for colors. every combo has its own, as the custom color can be different. */
284 store = gtk_tree_store_new(COL_COLOR_MAX, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_INT);
286 /* add 16 c64 colors */
287 gtk_tree_store_append(store, &parent, NULL);
288 gtk_tree_store_set(store, &parent, COL_COLOR_NAME, _("C64 Colors"), COL_COLOR_PIXBUF, NULL, -1);
289 for (int i = 0; i < 16; i++) {
290 GdkPixbuf *pixbuf;
292 pixbuf = color_combo_pixbuf_for_gd_color(GdColor::from_c64(i));
293 gtk_tree_store_append(store, &iter, &parent);
294 gtk_tree_store_set(store, &iter, COL_COLOR_ACTION, COLOR_ACTION_SELECT_C64, COL_COLOR_NAME, visible_name(GdColor::from_c64(i)).c_str(), COL_COLOR_PIXBUF, pixbuf, COL_COLOR_C64_INDEX, i, -1);
295 g_object_unref(pixbuf);
297 gtk_tree_store_append(store, &iter, NULL);
298 if (!selected_color_path)
299 selected_color_path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
300 gtk_tree_store_append(store, &iter, NULL);
301 gtk_tree_store_set(store, &iter, COL_COLOR_ACTION, COLOR_ACTION_SELECT_ATARI, COL_COLOR_NAME, _("Atari color..."), COL_COLOR_PIXBUF, NULL, -1);
302 gtk_tree_store_append(store, &iter, NULL);
303 gtk_tree_store_set(store, &iter, COL_COLOR_ACTION, COLOR_ACTION_SELECT_DTV, COL_COLOR_NAME, _("C64DTV color..."), COL_COLOR_PIXBUF, NULL, -1);
304 gtk_tree_store_append(store, &iter, NULL);
305 gtk_tree_store_set(store, &iter, COL_COLOR_ACTION, COLOR_ACTION_SELECT_RGB, COL_COLOR_NAME, _("RGB color..."), COL_COLOR_PIXBUF, NULL, -1);
307 combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
308 /* first column, object image */
309 renderer = gtk_cell_renderer_pixbuf_new();
310 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
311 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "pixbuf", COL_COLOR_PIXBUF, NULL);
312 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer, color_combo_set_sensitive, NULL, NULL);
313 /* second column, object name */
314 renderer = gtk_cell_renderer_text_new();
315 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
316 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer, color_combo_set_sensitive, NULL, NULL);
317 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", COL_COLOR_NAME, NULL);
318 gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(combo), color_combo_is_separator, NULL, NULL);
320 /* also a GdColor object is allocated for each combo. it will store its current value. */
321 /* it will be deleted by the destroy signal. */
322 GdColor *pcolor = new GdColor;
323 g_object_set_data(G_OBJECT(combo), GDASH_COLOR, pcolor);
325 gd_color_combo_set(GTK_COMBO_BOX(combo), initial);
326 g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(color_combo_changed), NULL);
327 g_signal_connect(G_OBJECT(combo), "destroy", G_CALLBACK(color_combo_destroyed), NULL);
329 return combo;
332 const GdColor &gd_color_combo_get_color(GtkWidget *widget) {
333 GdColor *pcolor = static_cast<GdColor *>(g_object_get_data(G_OBJECT(widget), GDASH_COLOR));
334 return *pcolor;
337 #undef GDASH_COLOR
338 #undef GDASH_COLOR_INDEX
341 #define GDASH_CELL "gdash-cell"
342 #define GDASH_ELEMENT "gdash-element"
343 #define GDASH_HOVER "gdash-hover"
344 #define GDASH_BUTTON "gdash-button"
346 #define GDASH_DIALOG "gdash-dialog"
347 #define GDASH_WINDOW_TITLE "gdash-window-title"
348 #define GDASH_DIALOG_VBOX "gdash-dialog-vbox"
350 #define GDASH_IMAGE "gdash-image"
351 #define GDASH_LABEL "gdash-label"
353 static int element_button_animcycle = 0;
355 /* this draws one element in the element selector box. */
356 static gboolean element_button_drawing_area_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
357 if (!widget->window)
358 return FALSE;
360 cairo_surface_t *cell = (cairo_surface_t *) g_object_get_data(G_OBJECT(widget), GDASH_CELL);
361 if (cell != NULL) {
362 cairo_t *cr = gdk_cairo_create(widget->window);
363 /* "The x and y parameters give the user-space coordinate at which the surface origin should appear." */
364 cairo_set_source_surface(cr, cell, 2, 2);
365 cairo_rectangle(cr, 2, 2, gdk_window_get_width(widget->window) - 4, gdk_window_get_height(widget->window));
366 cairo_fill(cr);
368 return TRUE;
371 /* this is called when entering and exiting a drawing area with the mouse. */
372 /* so they can be colored when the mouse is over. */
373 static gboolean element_button_drawing_area_crossing_event(GtkWidget *widget, GdkEventCrossing *event, gpointer data) {
374 g_object_set_data(G_OBJECT(widget), GDASH_HOVER, GINT_TO_POINTER(event->type == GDK_ENTER_NOTIFY));
375 return FALSE;
378 static gboolean element_button_redraw_timeout(gpointer data) {
379 GList *areas = (GList *)data;
381 element_button_animcycle = (element_button_animcycle + 1) % 8;
383 for (GList *iter = areas; iter != NULL; iter = iter->next) {
384 GtkWidget *da = (GtkWidget *)iter->data;
385 GdElementEnum element;
386 int draw;
387 gboolean hover;
389 /* which element is drawn? */
390 element = (GdElementEnum)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(da), GDASH_ELEMENT));
391 hover = (gboolean)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(da), GDASH_HOVER));
392 /* get pixbuf index */
393 draw = gd_element_properties[element].image;
394 if (draw < 0)
395 draw = -draw + element_button_animcycle;
396 if (hover)
397 draw += NUM_OF_CELLS;
398 /* set cell and queue draw if different from previous */
399 /* at first start, previous is null, so always different. */
400 if (g_object_get_data(G_OBJECT(da), GDASH_CELL) != editor_cell_renderer->cell_cairo_surface(draw)) {
401 g_object_set_data(G_OBJECT(da), GDASH_CELL, editor_cell_renderer->cell_cairo_surface(draw));
402 gtk_widget_queue_draw(da);
405 return TRUE;
409 void gd_element_button_set(GtkWidget *button, GdElementEnum element) {
410 GtkImage *image = GTK_IMAGE(g_object_get_data(G_OBJECT(button), GDASH_IMAGE));
411 gtk_image_set_from_pixbuf(image, editor_cell_renderer->combo_pixbuf(element));
413 GtkLabel *label = GTK_LABEL(g_object_get_data(G_OBJECT(button), GDASH_LABEL));
414 gtk_label_set_text(label, visible_name(element));
416 g_object_set_data(G_OBJECT(button), GDASH_ELEMENT, GINT_TO_POINTER(element));
419 static void element_button_da_clicked(GtkWidget *da, GdkEventButton *event, gpointer data) {
420 GtkDialog *dialog = GTK_DIALOG(data);
422 /* set the corresponding button to the element selected */
423 GdElementEnum element = (GdElementEnum)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(da), GDASH_ELEMENT));
424 GtkWidget *button = GTK_WIDGET(g_object_get_data(G_OBJECT(da), GDASH_BUTTON));
425 gd_element_button_set(button, element);
427 /* if this is a modal window, then it is a does-not-stay-open element box. */
428 /* so we issue a dialog response. */
429 if (gtk_window_get_modal(GTK_WINDOW(dialog)))
430 gtk_dialog_response(dialog, element);
431 else {
432 /* if not modal, it is a stay-open element box. */
433 /* close if left mouse button; stay open for others. */
434 if (event->button == 1)
435 gtk_widget_destroy(GTK_WIDGET(dialog));
439 static void element_button_dialog_destroyed_free_list(GtkWidget *dialog, gpointer data) {
440 GList *areas = (GList *) data;
442 g_source_remove_by_user_data(areas);
443 g_list_free(areas);
446 static void element_button_dialog_destroyed_null_pointer(GtkWidget *dialog, gpointer data) {
447 g_object_set_data(G_OBJECT(data), GDASH_DIALOG, NULL);
448 g_object_set_data(G_OBJECT(data), GDASH_DIALOG_VBOX, NULL);
451 static void element_button_dialog_close_button_clicked(GtkWidget *button, gpointer data) {
452 /* data is the dialog */
453 gtk_widget_destroy(GTK_WIDGET(data));
456 static void element_button_clicked_func(GtkWidget *button, gboolean stay_open) {
457 static GdElementEnum const elements[] = {
458 /* normal */
459 O_SPACE, O_DIRT, O_DIAMOND, O_STONE, O_MEGA_STONE, O_FLYING_DIAMOND, O_FLYING_STONE, O_NUT,
460 O_BRICK, O_FALLING_WALL, O_BRICK_EATABLE, O_BRICK_NON_SLOPED, O_SPACE, O_STEEL, O_STEEL_EATABLE, O_STEEL_EXPLODABLE,
462 O_INBOX, O_PRE_OUTBOX, O_PRE_INVIS_OUTBOX, O_PLAYER_GLUED, O_VOODOO, O_SPACE, O_SPACE, O_SKELETON,
463 O_WALLED_KEY_1, O_WALLED_KEY_2, O_WALLED_KEY_3, O_WALLED_DIAMOND, O_STEEL_SLOPED_UP_RIGHT, O_STEEL_SLOPED_UP_LEFT, O_STEEL_SLOPED_DOWN_LEFT, O_STEEL_SLOPED_DOWN_RIGHT,
465 O_AMOEBA, O_AMOEBA_2, O_SLIME, O_ACID, O_MAGIC_WALL, O_WATER, O_LAVA, O_REPLICATOR,
466 O_KEY_1, O_KEY_2, O_KEY_3, O_DIAMOND_KEY, O_BRICK_SLOPED_DOWN_RIGHT, O_BRICK_SLOPED_DOWN_LEFT, O_BRICK_SLOPED_UP_LEFT, O_BRICK_SLOPED_UP_RIGHT,
468 O_BOMB, O_CLOCK, O_POT, O_BOX, O_SWEET, O_PNEUMATIC_HAMMER, O_NITRO_PACK, O_ROCKET_LAUNCHER,
469 O_DOOR_1, O_DOOR_2, O_DOOR_3, O_TRAPPED_DIAMOND, O_DIRT_SLOPED_UP_RIGHT, O_DIRT_SLOPED_UP_LEFT, O_DIRT_SLOPED_DOWN_LEFT, O_DIRT_SLOPED_DOWN_RIGHT,
471 O_GRAVITY_SWITCH, O_CREATURE_SWITCH, O_BITER_SWITCH, O_EXPANDING_WALL_SWITCH, O_REPLICATOR_SWITCH, O_TELEPORTER, O_CONVEYOR_SWITCH, O_CONVEYOR_DIR_SWITCH,
472 O_H_EXPANDING_WALL, O_V_EXPANDING_WALL, O_EXPANDING_WALL, O_SPACE, O_DIRT2, O_DIRT_BALL, O_DIRT_LOOSE, O_NONE,
474 O_CONVEYOR_LEFT, O_CONVEYOR_RIGHT, O_SPACE, O_SPACE, O_SPACE, O_DIRT_GLUED, O_DIAMOND_GLUED, O_STONE_GLUED,
475 O_H_EXPANDING_STEEL_WALL, O_V_EXPANDING_STEEL_WALL, O_EXPANDING_STEEL_WALL, O_BLADDER_SPENDER, O_BLADDER, O_GHOST, O_WAITING_STONE, O_CHASING_STONE,
477 O_SPACE, O_FIREFLY_2, O_ALT_FIREFLY_2, O_SPACE, O_SPACE, O_BUTTER_2, O_ALT_BUTTER_2, O_SPACE, O_SPACE, O_STONEFLY_2, O_SPACE, O_COW_2, O_BITER_1, O_SPACE, O_SPACE, O_DRAGONFLY_2,
478 O_FIREFLY_1, O_FIREFLY_3, O_ALT_FIREFLY_1, O_ALT_FIREFLY_3, O_BUTTER_1, O_BUTTER_3, O_ALT_BUTTER_1, O_ALT_BUTTER_3, O_STONEFLY_1, O_STONEFLY_3, O_COW_1, O_COW_3, O_BITER_4, O_BITER_2, O_DRAGONFLY_1, O_DRAGONFLY_3,
479 O_SPACE, O_FIREFLY_4, O_ALT_FIREFLY_4, O_SPACE, O_SPACE, O_BUTTER_4, O_ALT_BUTTER_4, O_SPACE, O_SPACE, O_STONEFLY_4, O_SPACE, O_COW_4, O_BITER_3, O_SPACE, O_SPACE, O_DRAGONFLY_4,
481 /* for effects */
482 O_DIAMOND_F, O_STONE_F, O_MEGA_STONE_F, O_FLYING_DIAMOND_F, O_FLYING_STONE_F, O_FALLING_WALL_F, O_NITRO_PACK_F, O_NUT_F, O_PRE_PL_1, O_PRE_PL_2, O_PRE_PL_3, O_PLAYER, O_PLAYER_BOMB, O_PLAYER_STIRRING, O_OUTBOX, O_INVIS_OUTBOX,
484 O_BLADDER_1, O_BLADDER_2, O_BLADDER_3, O_BLADDER_4, O_BLADDER_5, O_BLADDER_6, O_BLADDER_7, O_BLADDER_8, O_SPACE,
485 O_COW_ENCLOSED_1, O_COW_ENCLOSED_2, O_COW_ENCLOSED_3, O_COW_ENCLOSED_4, O_COW_ENCLOSED_5, O_COW_ENCLOSED_6, O_COW_ENCLOSED_7,
487 O_WATER_1, O_WATER_2, O_WATER_3, O_WATER_4, O_WATER_5, O_WATER_6, O_WATER_7, O_WATER_8,
488 O_WATER_9, O_WATER_10, O_WATER_11, O_WATER_12, O_WATER_13, O_WATER_14, O_WATER_15, O_WATER_16,
490 O_BOMB_TICK_1, O_BOMB_TICK_2, O_BOMB_TICK_3, O_BOMB_TICK_4, O_BOMB_TICK_5, O_BOMB_TICK_6, O_BOMB_TICK_7,
491 O_BOMB_EXPL_1, O_BOMB_EXPL_2, O_BOMB_EXPL_3, O_BOMB_EXPL_4, O_NUT_CRACK_1, O_NUT_CRACK_2, O_NUT_CRACK_3, O_NUT_CRACK_4, O_UNKNOWN,
493 O_EXPLODE_1, O_EXPLODE_2, O_EXPLODE_3, O_EXPLODE_4, O_EXPLODE_5, O_TIME_PENALTY,
494 O_PRE_DIA_1, O_PRE_DIA_2, O_PRE_DIA_3, O_PRE_DIA_4, O_PRE_DIA_5, O_NITRO_PACK_EXPLODE, O_NITRO_EXPL_1, O_NITRO_EXPL_2, O_NITRO_EXPL_3, O_NITRO_EXPL_4,
495 O_PRE_STONE_1, O_PRE_STONE_2, O_PRE_STONE_3, O_PRE_STONE_4, O_PRE_STEEL_1, O_PRE_STEEL_2, O_PRE_STEEL_3, O_PRE_STEEL_4,
496 O_PRE_CLOCK_1, O_PRE_CLOCK_2, O_PRE_CLOCK_3, O_PRE_CLOCK_4, O_GHOST_EXPL_1, O_GHOST_EXPL_2, O_GHOST_EXPL_3, O_GHOST_EXPL_4,
498 O_ROCKET_1, O_ROCKET_2, O_ROCKET_3, O_ROCKET_4, O_PLAYER_ROCKET_LAUNCHER, O_SPACE, O_SPACE, O_SPACE,
499 O_SPACE, O_SPACE, O_SPACE, O_SPACE, O_SPACE, O_SPACE, O_SPACE, O_SPACE,
502 int cols = 16;
503 GList *areas = NULL;
505 /* if the dialog is already open, only show it. */
506 GtkWidget *dialog = (GtkWidget *)(g_object_get_data(G_OBJECT(button), GDASH_DIALOG));
507 if (dialog) {
508 gtk_window_present(GTK_WINDOW(dialog));
510 return;
513 /* elements dialog with no buttons; clicking on an element will do the trick. */
514 dialog = gtk_dialog_new();
515 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gtk_widget_get_toplevel(button)));
516 gtk_window_set_title(GTK_WINDOW(dialog), (char *) g_object_get_data(G_OBJECT(button), GDASH_WINDOW_TITLE));
517 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
518 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
519 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
520 /* associate the dialog with the button, so we know that it is open */
521 g_object_set_data(G_OBJECT(button), GDASH_DIALOG, dialog);
523 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
524 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox);
525 g_object_set_data(G_OBJECT(button), GDASH_DIALOG_VBOX, vbox);
527 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
528 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Normal elements")));
529 gtk_box_pack_start_defaults(GTK_BOX(vbox), align);
531 GtkWidget *table = gtk_table_new(0, 0, TRUE);
532 gtk_container_set_border_width(GTK_CONTAINER(table), 6);
533 gtk_box_pack_start_defaults(GTK_BOX(vbox), table);
535 GtkWidget *expander = gtk_expander_new(_("For effects"));
536 GtkWidget *table2 = gtk_table_new(0, 0, TRUE);
537 gtk_container_set_border_width(GTK_CONTAINER(table2), 6);
538 gtk_container_add(GTK_CONTAINER(expander), table2);
539 gtk_box_pack_start_defaults(GTK_BOX(vbox), expander);
541 /* color for background around elements. */
542 /* gdkcolors are 16bit, we should *256, but instead *200 so a bit darker. */
543 GdkColor c;
544 unsigned char r, g, b;
545 editor_cell_renderer->background_color().get_rgb(r, g, b);
546 c.red = r * 200;
547 c.green = g * 200;
548 c.blue = b * 200;
549 /* create drawing areas */
550 int pcs = editor_cell_renderer->get_cell_size();
551 int into_second = 0;
552 for (unsigned i = 0; i < G_N_ELEMENTS(elements); i++) {
553 GtkWidget *da = gtk_drawing_area_new();
554 gtk_widget_modify_bg(da, GTK_STATE_NORMAL, &c);
555 areas = g_list_prepend(areas, da); /* put in list for animation timeout, that one will request redraw on them */
556 gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
557 g_object_set_data(G_OBJECT(da), GDASH_ELEMENT, GINT_TO_POINTER(elements[i]));
558 g_object_set_data(G_OBJECT(da), GDASH_BUTTON, button); /* button to update on click */
559 gtk_widget_set_size_request(da, pcs + 4, pcs + 4); /* 2px border around them */
560 gtk_widget_set_tooltip_text(da, visible_name(elements[i]));
561 g_signal_connect(G_OBJECT(da), "expose-event", G_CALLBACK(element_button_drawing_area_expose_event), GINT_TO_POINTER(elements[i]));
562 g_signal_connect(G_OBJECT(da), "leave-notify-event", G_CALLBACK(element_button_drawing_area_crossing_event), NULL);
563 g_signal_connect(G_OBJECT(da), "enter-notify-event", G_CALLBACK(element_button_drawing_area_crossing_event), NULL);
564 g_signal_connect(G_OBJECT(da), "button-press-event", G_CALLBACK(element_button_da_clicked), dialog);
565 if (elements[i] == O_DIAMOND_F)
566 /* the dirt2 the first element to be put in the effect list; from that one, always use table2 */
567 into_second = i;
568 if (!into_second)
569 gtk_table_attach_defaults(GTK_TABLE(table), GTK_WIDGET(da), i % cols, i % cols + 1, i / cols, i / cols + 1);
570 else {
571 int j = i - into_second;
573 gtk_table_attach_defaults(GTK_TABLE(table2), GTK_WIDGET(da), j % cols, j % cols + 1, j / cols, j / cols + 1);
578 /* add a timeout which animates the drawing areas */
579 g_timeout_add(40, element_button_redraw_timeout, areas);
580 /* if the dialog is destroyed, we must free the list which contains the drawing areas */
581 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(element_button_dialog_destroyed_free_list), areas);
582 /* also remember that the button no longer has its own dialog open */
583 g_signal_connect(G_OBJECT(dialog), "destroy", G_CALLBACK(element_button_dialog_destroyed_null_pointer), button);
585 if (!stay_open) {
586 gtk_widget_show_all(dialog);
587 gtk_dialog_run(GTK_DIALOG(dialog));
588 gtk_widget_destroy(dialog);
589 } else {
590 /* if it is a stay-open element box, add a button which (also) closes it */
591 GtkWidget *close_button;
593 close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
594 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area), close_button, FALSE, FALSE, 0);
595 g_signal_connect(G_OBJECT(close_button), "clicked", (GCallback) element_button_dialog_close_button_clicked, dialog);
597 gtk_widget_show_all(dialog);
598 gtk_window_present_with_time(GTK_WINDOW(dialog), gtk_get_current_event_time());
602 GdElementEnum gd_element_button_get(GtkWidget *button) {
603 gpointer element = g_object_get_data(G_OBJECT(button), GDASH_ELEMENT);
604 return (GdElementEnum)GPOINTER_TO_INT(element);
607 /* the pixbufs might be changed during the lifetime of an element button. *
608 * for example, when colors of a cave are redefined. so this is made
609 * global and can be called. */
610 void gd_element_button_update_pixbuf(GtkWidget *button) {
611 /* set the same as it already shows, but pixbuf update will be triggered by this */
612 gd_element_button_set(button, gd_element_button_get(button));
615 static void element_button_clicked_stay_open(GtkWidget *button, gpointer data) {
616 element_button_clicked_func(button, TRUE);
619 static void element_button_clicked_modal(GtkWidget *button, gpointer data) {
620 element_button_clicked_func(button, FALSE);
623 /* set the title of the window associated with this element button. */
624 /* if the window is already open, also set the title there. */
625 void gd_element_button_set_dialog_title(GtkWidget *button, const char *title) {
626 /* get original title, and free if needed */
627 char *old_title = (char *) g_object_get_data(G_OBJECT(button), GDASH_WINDOW_TITLE);
628 g_free(old_title);
629 /* remember new title */
630 g_object_set_data(G_OBJECT(button), GDASH_WINDOW_TITLE, title ? g_strdup(title) : g_strdup(_("Elements")));
632 /* if it has its own window open at the moment, also set it */
633 GtkWidget *dialog = (GtkWidget *) g_object_get_data(G_OBJECT(button), GDASH_DIALOG);
634 if (dialog)
635 gtk_window_set_title(GTK_WINDOW(dialog), title);
639 void gd_element_button_set_dialog_sensitive(GtkWidget *button, gboolean sens) {
640 GtkWidget *vbox = (GtkWidget *) g_object_get_data(G_OBJECT(button), GDASH_DIALOG_VBOX);
641 if (vbox)
642 gtk_widget_set_sensitive(vbox, sens);
645 /* frees the special title text, and optionally destroys the dialog, if exists. */
646 static void element_button_destroyed(GtkWidget *button, gpointer data) {
647 char *title = (char *) g_object_get_data(G_OBJECT(button), GDASH_WINDOW_TITLE);
648 g_free(title);
649 /* if it has a dialog open for any reason, close that also */
650 GtkWidget *dialog = (GtkWidget *) g_object_get_data(G_OBJECT(button), GDASH_DIALOG);
651 if (dialog)
652 gtk_widget_destroy(dialog);
656 * creates a new element button. optionally the dialog created by
657 * the particular button can stay open, and accept many clicks.
658 * also, it can have some title line, like "draw element"
660 GtkWidget *gd_element_button_new(GdElementEnum initial_element, gboolean stays_open, const char *special_title) {
661 // the button
662 GtkWidget *button = gtk_button_new();
664 // the contents - icon of elemen + name
665 GtkWidget *hbox = gtk_hbox_new(FALSE, 3);
666 gtk_container_add(GTK_CONTAINER(button), hbox);
667 GtkWidget *image = gtk_image_new();
668 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
669 GtkWidget *label = gd_label_new_leftaligned("");
670 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
671 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
672 g_object_set_data(G_OBJECT(button), GDASH_IMAGE, image);
673 g_object_set_data(G_OBJECT(button), GDASH_LABEL, label);
675 g_signal_connect(G_OBJECT(button), "destroy", (GCallback) element_button_destroyed, NULL);
676 /* minimum width 96px */
677 gtk_widget_set_size_request(button, 96, -1);
678 if (stays_open)
679 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(element_button_clicked_stay_open), NULL);
680 else
681 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(element_button_clicked_modal), NULL);
683 /* set the associated string which will be the title of the element box window opened */
684 gd_element_button_set_dialog_title(button, special_title);
686 gd_element_button_set(button, initial_element);
687 return button;
690 #undef GDASH_IMAGE
691 #undef GDASH_LABEL
692 #undef GDASH_WINDOW_TITLE
693 #undef GDASH_DIALOG_VBOX
694 #undef GDASH_DIALOG
695 #undef GDASH_CELL
696 #undef GDASH_ELEMENT
697 #undef GDASH_HOVER
698 #undef GDASH_BUTTON
701 /* directions to be shown, and corresponding icons. */
702 static GdDirection const direction_combo_shown_directions[] = { MV_UP, MV_RIGHT, MV_DOWN, MV_LEFT };
703 static const char *direction_combo_shown_icons[] = { GTK_STOCK_GO_UP, GTK_STOCK_GO_FORWARD, GTK_STOCK_GO_DOWN, GTK_STOCK_GO_BACK };
705 GtkWidget *gd_direction_combo_new(GdDirectionEnum initial) {
706 static GtkListStore *store = NULL;
707 enum {
708 DIR_PIXBUF_COL,
709 DIR_TEXT_COL,
710 NUM_DIR_COLS
713 // create list, if did not do so far. a single one can be used for multiple combo boxes.
714 if (!store) {
715 // this cell view is used to render the icons
716 GtkWidget *cellview = gtk_cell_view_new();
717 store = gtk_list_store_new(NUM_DIR_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING);
719 for (unsigned i = 0; i < G_N_ELEMENTS(direction_combo_shown_directions); i++) {
720 GtkTreeIter iter;
721 gtk_list_store_append(store, &iter);
722 GdkPixbuf *pixbuf = gtk_widget_render_icon(cellview, direction_combo_shown_icons[i], GTK_ICON_SIZE_MENU, NULL);
723 gtk_list_store_set(store, &iter, DIR_PIXBUF_COL, pixbuf, DIR_TEXT_COL, visible_name(direction_combo_shown_directions[i]), -1);
726 gtk_widget_destroy(cellview);
729 /* create combo box and renderer for icon and text */
730 GtkWidget *combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
731 GtkCellRenderer *renderer;
732 renderer = gtk_cell_renderer_pixbuf_new();
733 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
734 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "pixbuf", DIR_PIXBUF_COL, NULL);
735 renderer = gtk_cell_renderer_text_new();
736 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
737 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", DIR_TEXT_COL, NULL);
739 /* set to initial value */
740 /* we have to find it in the array */
741 for (unsigned i = 0; i < G_N_ELEMENTS(direction_combo_shown_directions); i++)
742 if (direction_combo_shown_directions[i] == initial)
743 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
745 return combo;
748 GdDirectionEnum gd_direction_combo_get_direction(GtkWidget *combo) {
749 return (GdDirectionEnum) direction_combo_shown_directions[gtk_combo_box_get_active(GTK_COMBO_BOX(combo))];
753 /*****************************************************/
756 GtkWidget *gd_scheduling_combo_new(GdSchedulingEnum initial) {
757 GtkWidget *combo;
759 /* no icons, so a simple text combo box will suffice. */
760 combo = gtk_combo_box_new_text();
762 for (int i = 0; i < GD_SCHEDULING_MAX; i++)
763 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), visible_name((GdSchedulingEnum) i));
765 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), initial);
767 return combo;
770 GdSchedulingEnum gd_scheduling_combo_get_scheduling(GtkWidget *combo) {
771 return (GdSchedulingEnum)(gtk_combo_box_get_active(GTK_COMBO_BOX(combo)));