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.
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
;
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 */
59 COLOR_ACTION_SELECT_C64
,
60 COLOR_ACTION_SELECT_ATARI
,
61 COLOR_ACTION_SELECT_DTV
,
62 COLOR_ACTION_SELECT_RGB
,
66 * creates a small pixbuf with the specified color
69 color_combo_pixbuf_for_gd_color(const GdColor
&col
) {
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
;
79 pixel
= (guint32(r
) << 24) + (guint32(g
) << 16) + (guint32(b
) << 8);
80 gdk_pixbuf_fill(pixbuf
, pixel
);
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. */
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 */
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
);
100 gtk_combo_box_set_active_iter(combo
, &iter
);
102 GtkTreeModel
*model
= gtk_combo_box_get_model(GTK_COMBO_BOX(combo
));
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
);
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
)));
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
));
132 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo
), &iter
);
133 gtk_tree_model_get(model
, &iter
, COL_COLOR_ACTION
, &action
, -1);
135 case COLOR_ACTION_SELECT_RGB
: {
137 GtkColorSelection
*colorsel
;
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
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
) {
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));
165 /* if not accepted, return button to original state */
166 gd_color_combo_set(GTK_COMBO_BOX(combo
), prevcol
);
169 gtk_widget_destroy(dialog
);
173 case COLOR_ACTION_SELECT_ATARI
:
174 case COLOR_ACTION_SELECT_DTV
: {
175 GtkWidget
*dialog
, *table
, *frame
;
179 GdColor(*colorfunc
)(unsigned int i
);
183 case COLOR_ACTION_SELECT_ATARI
:
184 // TRANSLATORS: Title text capitalization in English
185 title
= _("Select Atari Color");
186 colorfunc
= &GdColor::from_atari
;
188 case COLOR_ACTION_SELECT_DTV
:
189 // TRANSLATORS: Title text capitalization in English
190 title
= _("Select C64 DTV Color");
191 colorfunc
= &GdColor::from_c64dtv
;
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
++) {
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
;
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
);
225 result
= gtk_dialog_run(GTK_DIALOG(dialog
));
227 gd_color_combo_set(GTK_COMBO_BOX(combo
), colorfunc(result
));
229 /* if not accepted, return button to original state */
230 gd_color_combo_set(GTK_COMBO_BOX(combo
), prevcol
);
232 gtk_widget_destroy(dialog
);
236 case COLOR_ACTION_SELECT_C64
: {
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] */
244 case COLOR_ACTION_NONE
:
245 /* do nothing - this is the special color, eg. rgb. for that, we already updated pcolor and the like */
251 /* we use a non-visible, non-selectable row for the currently selected color (unless it is a c64 color). */
253 color_combo_is_separator(GtkTreeModel
*model
, GtkTreeIter
*iter
, gpointer data
) {
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
);
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
));
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 */
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
++) {
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
);
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
));
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
) {
360 cairo_surface_t
*cell
= (cairo_surface_t
*) g_object_get_data(G_OBJECT(widget
), GDASH_CELL
);
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
));
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
));
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
;
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
;
395 draw
= -draw
+ element_button_animcycle
;
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
);
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
);
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
);
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
[] = {
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
,
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
,
505 /* if the dialog is already open, only show it. */
506 GtkWidget
*dialog
= (GtkWidget
*)(g_object_get_data(G_OBJECT(button
), GDASH_DIALOG
));
508 gtk_window_present(GTK_WINDOW(dialog
));
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. */
544 unsigned char r
, g
, b
;
545 editor_cell_renderer
->background_color().get_rgb(r
, g
, b
);
549 /* create drawing areas */
550 int pcs
= editor_cell_renderer
->get_cell_size();
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 */
569 gtk_table_attach_defaults(GTK_TABLE(table
), GTK_WIDGET(da
), i
% cols
, i
% cols
+ 1, i
/ cols
, i
/ cols
+ 1);
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
);
586 gtk_widget_show_all(dialog
);
587 gtk_dialog_run(GTK_DIALOG(dialog
));
588 gtk_widget_destroy(dialog
);
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
);
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
);
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
);
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
);
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
);
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
) {
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);
679 g_signal_connect(G_OBJECT(button
), "clicked", G_CALLBACK(element_button_clicked_stay_open
), NULL
);
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
);
692 #undef GDASH_WINDOW_TITLE
693 #undef GDASH_DIALOG_VBOX
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
;
713 // create list, if did not do so far. a single one can be used for multiple combo boxes.
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
++) {
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
);
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
) {
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
);
770 GdSchedulingEnum
gd_scheduling_combo_get_scheduling(GtkWidget
*combo
) {
771 return (GdSchedulingEnum
)(gtk_combo_box_get_active(GTK_COMBO_BOX(combo
)));