2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #include <glib/gi18n.h>
24 #include "cave/cavestored.hpp"
25 #include "gtk/gtkui.hpp"
26 #include "gtk/gtkuisettings.hpp"
27 #include "cave/object/caveobject.hpp"
28 #include "cave/caveset.hpp"
29 #include "misc/printf.hpp"
30 #include "gtk/help.hpp"
31 #include "gtk/gtkscreen.hpp"
32 #include "editor/editorwidgets.hpp"
33 #include "editor/editorautowidgets.hpp"
34 #include "editor/exportcrli.hpp"
35 #include "editor/exporthtml.hpp"
36 #include "misc/logger.hpp"
37 #include "fileops/c64import.hpp"
38 #include "cave/gamerender.hpp"
39 #include "misc/util.hpp"
40 #include "cave/elementproperties.hpp"
41 #include "editor/editorcellrenderer.hpp"
42 #include "editor/exporthtml.hpp"
43 #include "settings.hpp"
44 #include "gtk/gtkpixbuffactory.hpp"
45 #include "framework/commands.hpp"
46 #include "framework/gameactivity.hpp"
47 #include "cave/gamecontrol.hpp"
48 #include "cave/titleanimation.hpp"
50 #include "sdl/sdlmainwindow.hpp"
51 #include "gtk/gtkmainwindow.hpp"
53 #include "cave/object/caveobjectboundaryfill.hpp"
54 #include "cave/object/caveobjectcopypaste.hpp"
55 #include "cave/object/caveobjectfillrect.hpp"
56 #include "cave/object/caveobjectfloodfill.hpp"
57 #include "cave/object/caveobjectjoin.hpp"
58 #include "cave/object/caveobjectline.hpp"
59 #include "cave/object/caveobjectmaze.hpp"
60 #include "cave/object/caveobjectpoint.hpp"
61 #include "cave/object/caveobjectrandomfill.hpp"
62 #include "cave/object/caveobjectraster.hpp"
63 #include "cave/object/caveobjectrectangle.hpp"
65 #include "editor/editor.hpp"
72 TOOL_FILLED_RECTANGLE
,
75 TOOL_FLOODFILL_REPLACE
,
76 TOOL_FLOODFILL_BORDER
,
87 static GtkWidget
*caveset_popup
, *object_list_popup
, *drawing_area_popup
, *level_scale
, *new_object_level_combo
;
88 static GtkActionGroup
*actions
, *actions_edit_tools
, *actions_edit_cave
, *actions_edit_caveset
, *actions_edit_map
,
89 *actions_edit_random
, *actions_edit_object
, *actions_edit_one_object
, *actions_cave_selector
, *actions_toggle
,
90 *actions_clipboard_paste
, *actions_edit_undo
, *actions_edit_redo
, *actions_clipboard
;
91 static GtkWidget
*gd_editor_window
;
92 static GTKPixbufFactory
*editor_pixbuf_factory
;
93 EditorCellRenderer
*editor_cell_renderer
;
94 static guint timeout_id
;
96 static EditTool action
; /* activated edit tool, like move, plot, line... can be a gdobject, or there are other indexes which have meanings. */
97 static int edit_level
; /* level shown in the editor... does not really affect editing */
99 static int clicked_x
, clicked_y
, mouse_x
, mouse_y
; /* variables for mouse movement handling */
100 static gboolean button1_clicked
; /* true if we got button1 press event, then set to false on release */
102 static std::list
<CaveStored
> undo_caves
, redo_caves
;
103 static gboolean undo_move_flag
=FALSE
; /* this is set to false when the mouse is clicked. on any movement, undo is saved and set to true */
105 static CaveStored
*edited_cave
; /* cave data actually edited in the editor */
106 static CaveRendered
*rendered_cave
; /* a cave with all objects rendered, and to be drawn */
108 static CaveObjectStore object_clipboard
; /* cave object clipboard. */
109 static std::list
<CaveStored
> cave_clipboard
; /* copied caves */
112 static GtkWidget
*scroll_window
, *scroll_window_objects
;
113 static GtkWidget
*iconview_cavelist
, *drawing_area
;
114 static GTKScreen
*screen
;
115 static GtkWidget
*toolbars
;
116 static GtkWidget
*element_button
, *fillelement_button
;
117 static GtkWidget
*label_coordinate
, *label_object
, *label_first_element
, *label_second_element
;
118 static GHashTable
*cave_pixbufs
; /* currently better choice than std::map as it has a destroy notifier to free the pixbufs */
120 static CaveMap
<int> gfx_buffer
;
121 static CaveMap
<bool> object_highlight_map
;
123 static GtkListStore
*object_list
;
124 static GtkWidget
*object_list_tree_view
;
125 static std::set
<CaveObject
*> selected_objects
;
127 static CaveSet
*caveset
; ///< The caveset edited.
130 enum ObjectListColumns
{
132 LEVELS_PIXBUF_COLUMN
,
134 ELEMENT_PIXBUF_COLUMN
,
141 enum CaveIconViewColumns
{
149 struct GdObjectDescription
{
150 const char *first_button
, *second_button
;
153 /* description of coordinates, elements - used by editor properties dialog. */
154 static GdObjectDescription gd_object_description
[]={
155 /* for drawing objects */
156 /* plot */ { N_("Draw"), NULL
},
157 /* line */ { N_("Draw"), NULL
},
158 /* rect */ { N_("Draw"), NULL
},
159 /* fldr */ { N_("Draw"), N_("Fill") },
160 /* rast */ { N_("Draw"), NULL
},
161 /* join */ { N_("Find"), N_("Draw") },
162 /* fldf */ { N_("Fill"), NULL
},
163 /* bouf */ { N_("Border"), N_("Fill") },
164 /* maze */ { N_("Wall"), N_("Path") },
165 /* umaz */ { N_("Wall"), N_("Path") },
166 /* bmaz */ { N_("Wall"), N_("Path") },
167 /* rndf */ { NULL
, NULL
},
168 /* cpps */ { NULL
, NULL
},
169 /* for other tools */
170 /* move */ { NULL
, NULL
},
171 /* free */ { N_("Draw"), NULL
},
172 /* visi */ { NULL
, NULL
},
175 /* edit tools; check variable "action". this is declared here, as it stores the names of cave drawing objects */
176 /* this is also used to give objects an icon, so the first part of this array must correspond to the CaveObject::Type enum! */
177 static GtkRadioActionEntry action_objects
[]={
178 {"Plot", GD_ICON_EDITOR_POINT
, N_("_Point"), "F2", N_("Draw single element"), TOOL_POINT
},
179 {"Line", GD_ICON_EDITOR_LINE
, N_("_Line"), "F4", N_("Draw line of elements"), TOOL_LINE
},
180 {"Rectangle", GD_ICON_EDITOR_RECTANGLE
, N_("_Outline"), "F5", N_("Draw rectangle outline"), TOOL_RECTANGLE
},
181 {"FilledRectangle", GD_ICON_EDITOR_FILLRECT
, N_("R_ectangle"), "F6", N_("Draw filled rectangle"), TOOL_FILLED_RECTANGLE
},
182 {"Raster", GD_ICON_EDITOR_RASTER
, N_("Ra_ster"), NULL
, N_("Draw raster"), TOOL_RASTER
},
183 {"Join", GD_ICON_EDITOR_JOIN
, N_("_Join"), NULL
, N_("Draw join"), TOOL_JOIN
},
184 {"FloodFillReplace", GD_ICON_EDITOR_FILL_REPLACE
, N_("_Flood fill"), NULL
, N_("Fill by replacing elements"), TOOL_FLOODFILL_REPLACE
},
185 {"FloodFillBorder", GD_ICON_EDITOR_FILL_BORDER
, N_("_Boundary fill"), NULL
, N_("Fill a closed area"), TOOL_FLOODFILL_BORDER
},
186 {"Maze", GD_ICON_EDITOR_MAZE
, N_("Ma_ze"), NULL
, N_("Draw maze"), TOOL_MAZE
},
187 {"UnicursalMaze", GD_ICON_EDITOR_MAZE_UNI
, N_("U_nicursal maze"), NULL
, N_("Draw unicursal maze"), TOOL_MAZE_UNICURSAL
},
188 {"BraidMaze", GD_ICON_EDITOR_MAZE_BRAID
, N_("Bra_id maze"), NULL
, N_("Draw braid maze"), TOOL_MAZE_BRAID
},
189 {"RandomFill", GD_ICON_RANDOM_FILL
, N_("R_andom fill"), NULL
, N_("Draw random elements"), TOOL_RANDOM_FILL
},
190 {"CopyPaste", GTK_STOCK_COPY
, N_("_Copy and Paste"), "" /* deliberately not null or it would use ctrl+c hotkey */, N_("Copy and paste area"), TOOL_COPY_PASTE
},
192 {"Move", GD_ICON_EDITOR_MOVE
, N_("_Move"), "F1", N_("Move object"), TOOL_MOVE
},
193 {"Freehand", GD_ICON_EDITOR_FREEHAND
, N_("F_reehand"), "F3", N_("Draw freely"), TOOL_FREEHAND
},
194 {"Visible", GTK_STOCK_ZOOM_FIT
, N_("Set _visible region"), NULL
, N_("Select visible region of cave during play"), TOOL_VISIBLE_REGION
},
198 /* forward function declarations */
199 static void set_status_label_for_cave(CaveStored
*cave
);
200 static void select_cave_for_edit(CaveStored
*);
201 static void select_tool(int tool
);
202 static void render_cave();
203 static void object_properties(CaveObject
*object
);
205 static struct NewObjectVisibleOn
{
208 } new_objects_visible_on
[] = {
209 { N_("All levels"), 0 },
210 { N_("Level 2 and up"), 1 },
211 { N_("Level 3 and up"), 2 },
212 { N_("Level 4 and up"), 3 },
213 { N_("Level 5 only"), 4 },
217 /*****************************************
219 * which shows objects in a cave,
220 * and also stores current selection.
223 static gboolean object_list_selection_changed_signal_disabled
=FALSE
;
225 static bool object_list_is_selected(CaveObject
*o
) {
226 return selected_objects
.count(o
)!=0;
229 static bool object_list_is_not_selected(CaveObject
*o
) {
230 return selected_objects
.count(o
)==0;
233 static CaveObjectStore
234 object_list_copy_of_selected() {
235 g_assert(edited_cave
!=NULL
);
238 for (std::set
<CaveObject
*>::const_iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
)
239 l
.push_back_adopt((*it
)->clone()); /* save copy in list */
245 object_list_count_selected() {
246 return selected_objects
.size();
250 object_list_is_any_selected() {
251 return !selected_objects
.empty();
254 /* this function is to be used only when there is one object in the set. */
256 object_list_first_selected() {
257 if (!object_list_is_any_selected())
260 return *selected_objects
.begin();
264 object_list_clear_selection() {
265 if (object_list_is_any_selected()) {
266 selected_objects
.clear();
267 object_list_selection_changed_signal_disabled
= TRUE
;
268 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
)));
269 object_list_selection_changed_signal_disabled
= FALSE
;
275 /* check if current iter points to the same object as data. if it is, select the iter */
277 object_list_select_object_func(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
, gpointer data
) {
279 gtk_tree_model_get(model
, iter
, POINTER_COLUMN
, &object
, -1);
281 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW (object_list_tree_view
)), iter
);
288 object_list_add_to_selection(CaveObject
*object
) {
289 gtk_tree_model_foreach(GTK_TREE_MODEL(object_list
), (GtkTreeModelForeachFunc
) object_list_select_object_func
, object
);
292 /* check if current iter points to the same object as data. if it is, select the iter */
294 object_list_unselect_object_func(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
, gpointer data
) {
297 gtk_tree_model_get(model
, iter
, POINTER_COLUMN
, &object
, -1);
299 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW (object_list_tree_view
)), iter
);
306 object_list_remove_from_selection(CaveObject
*object
) {
307 gtk_tree_model_foreach(GTK_TREE_MODEL (object_list
), (GtkTreeModelForeachFunc
) object_list_unselect_object_func
, object
);
312 object_list_select_one_object(CaveObject
*object
) {
313 object_list_clear_selection();
314 object_list_add_to_selection(object
);
318 object_list_selection_changed_signal(GtkTreeSelection
*selection
, gpointer data
) {
319 if (object_list_selection_changed_signal_disabled
)
322 /* check all selected objects, and set all selected objects to highlighted */
324 GList
*selected_rows
=gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
)), &model
);
325 selected_objects
.clear();
326 for (GList
*siter
=selected_rows
; siter
!=NULL
; siter
=siter
->next
) {
327 GtkTreePath
*path
=(GtkTreePath
*) siter
->data
;
329 gtk_tree_model_get_iter(model
, &iter
, path
);
331 gtk_tree_model_get(model
, &iter
, POINTER_COLUMN
, &object
, -1);
332 selected_objects
.insert(object
);
334 g_list_foreach(selected_rows
, (GFunc
) gtk_tree_path_free
, NULL
);
335 g_list_free(selected_rows
);
337 /* object highlight all to false */
338 object_highlight_map
.fill(false);
340 /* check all selected objects, and set all selected objects to highlighted */
341 for (std::set
<CaveObject
*>::const_iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
) {
342 for (int y
=0; y
<rendered_cave
->h
; y
++)
343 for (int x
=0; x
<rendered_cave
->w
; x
++)
344 if (rendered_cave
->objects_order(x
, y
)==(*it
)) /* if element at x,y was created by this object... */
345 object_highlight_map(x
, y
)=true;
348 /* how many selected objects? */
349 int count
=selected_objects
.size();
352 gtk_action_group_set_sensitive(actions_edit_one_object
, count
==1);
353 gtk_action_group_set_sensitive(actions_edit_object
, count
!=0);
354 gtk_action_group_set_sensitive(actions_clipboard
, count
!=0);
357 /* NO object selected -> show general cave info */
358 set_status_label_for_cave(edited_cave
);
361 /* exactly ONE object is selected */
362 CaveObject
*object
=object_list_first_selected();
364 std::string text
=object
->get_description_markup();
365 gtk_label_set_markup(GTK_LABEL(label_object
), text
.c_str());
368 /* more than one object is selected */
369 gtk_label_set_markup(GTK_LABEL(label_object
), CPrintf(_("%d objects selected")) % count
);
373 /* for popup menu, by properties key */
375 object_list_show_popup_menu(GtkWidget
*widget
, gpointer data
) {
376 gtk_menu_popup(GTK_MENU(object_list_popup
), NULL
, NULL
, NULL
, NULL
, 0, gtk_get_current_event_time());
379 /* for popup menu, by right-click */
381 object_list_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
) {
382 if (event
->type
==GDK_BUTTON_PRESS
&& event
->button
==3) {
383 gtk_menu_popup(GTK_MENU(object_list_popup
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
390 * SIGNALS which are used when the object list is reordered by drag and drop.
393 * drag and drop emits the following signals: insert, changed, and delete.
394 * so there is a moment when the reordered row has TWO copies in the model.
395 * therefore we cannot use the changed signal to see the new order.
397 * we also cannot use the delete signal on its own, as it is also emitted when
398 * rendering the cave.
400 * instead, we connect to the changed and the delete signal. we have a flag,
401 * named "rowchangedbool", which is set to true by the changed signal, and
402 * therefore we know that the next delete signal is emitted by a reordering.
403 * at that point, we have the new order, and _AFTER_ resetting the flag,
404 * we rerender the cave.
406 static gboolean object_list_row_changed_bool
=FALSE
;
409 object_list_row_changed(GtkTreeModel
*model
, GtkTreePath
*changed_path
, GtkTreeIter
*changed_iter
, gpointer user_data
) {
410 /* set the flag so we know that the next delete signal is emitted by a drag-and-drop */
411 object_list_row_changed_bool
=TRUE
;
415 object_list_new_order_func(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
, gpointer data
) {
416 CaveObjectStore
*list
=static_cast<CaveObjectStore
*>(data
);
419 gtk_tree_model_get(model
, iter
, POINTER_COLUMN
, &object
, -1);
420 list
->push_back_adopt(object
->clone());
422 return FALSE
; /* continue traversing */
425 /* XXX this does not reorder, rather creates a brand new list... */
427 object_list_row_delete(GtkTreeModel
*model
, GtkTreePath
*changed_path
, gpointer user_data
) {
428 if (object_list_row_changed_bool
) {
429 CaveObjectStore new_order
;
431 /* reset the flag, as we handle the reordering here - so its signal handler will not interfere */
432 object_list_row_changed_bool
=FALSE
;
434 /* this will build the new object order to the list */
435 gtk_tree_model_foreach(model
, object_list_new_order_func
, &new_order
);
437 edited_cave
->objects
=new_order
;
439 object_list_clear_selection(); /* XXX */
445 /* row activated - set properties of object */
447 object_list_row_activated(GtkTreeView
*tree_view
, GtkTreePath
*path
, GtkTreeViewColumn
*column
, gpointer user_data
) {
448 GtkTreeModel
*model
=gtk_tree_view_get_model(tree_view
);
452 gtk_tree_model_get_iter(model
, &iter
, path
);
453 gtk_tree_model_get(model
, &iter
, POINTER_COLUMN
, &object
, -1);
454 object_properties(object
);
455 /* render cave after changing object properties */
461 help_cb(GtkWidget
*widget
, gpointer data
) {
462 gd_show_editor_help (gd_editor_window
);
466 /****************************************************
477 /* delete the cave copies saved for undo. */
484 /* save a copy of the current state of edited cave. this is to be used
485 internally; as it does not delete redo. */
486 #define UNDO_STEPS 10
488 undo_save_current_state() {
489 /* if more than four, forget some (should only forget one) */
490 while (undo_caves
.size() >= UNDO_STEPS
)
491 undo_caves
.pop_front();
493 undo_caves
.push_back(*edited_cave
);
496 /* save a copy of the current state of edited cave, after some operation.
497 this destroys the redo list, as from that point that is useless. */
500 g_return_if_fail(edited_cave
!=NULL
);
502 /* we also use this function to set the edited flag, as it is called for any action */
503 caveset
->edited
=TRUE
;
505 /* remove from pixbuf hash: delete its pixbuf */
506 /* as now we know that this cave is really edited. */
507 g_hash_table_remove(cave_pixbufs
, edited_cave
);
509 undo_save_current_state();
512 /* now we have a cave to do an undo from, so sensitize the menu item */
513 gtk_action_group_set_sensitive (actions_edit_undo
, TRUE
);
514 gtk_action_group_set_sensitive (actions_edit_redo
, FALSE
);
517 static void undo_do_one_step() {
518 /* push current to redo list. we do not check the redo list size, as the undo size limits it automatically... */
519 redo_caves
.push_front(*edited_cave
);
521 /* copy to edited one, and free backup */
522 *edited_cave
=undo_caves
.back();
523 undo_caves
.pop_back();
525 /* call to renew editor window */
526 select_cave_for_edit(edited_cave
);
529 /* this function is for "internal" use.
530 * some cave editing functions are much easier
531 * if they can use the undo facility when
532 * the user presses a cancel button.
533 * in that case, that undo step should not be
534 * visible in the redo list.
536 static void undo_do_one_step_but_no_redo() {
537 /* copy to edited one, and free backup */
538 *edited_cave
=undo_caves
.back();
539 undo_caves
.pop_back();
541 /* call to renew editor window */
542 select_cave_for_edit(edited_cave
);
545 /* do the undo - callback */
547 undo_cb(GtkWidget
*widget
, gpointer data
) {
548 g_return_if_fail(!undo_caves
.empty());
549 g_return_if_fail(edited_cave
!=NULL
);
554 /* do the redo - callback */
556 redo_cb(GtkWidget
*widget
, gpointer data
) {
557 g_return_if_fail(!redo_caves
.empty());
558 g_return_if_fail(edited_cave
!=NULL
);
560 /* push back current to undo */
561 undo_save_current_state();
563 /* and select the first from redo */
564 *edited_cave
=redo_caves
.front();
565 redo_caves
.pop_front();
567 /* call to renew editor window */
568 select_cave_for_edit(edited_cave
);
572 static void editor_window_set_title() {
574 /* editing a specific cave */
575 gtk_window_set_title(GTK_WINDOW(gd_editor_window
), CPrintf(_("GDash Cave Editor - %s")) % edited_cave
->name
);
577 /* editing the caveset */
578 gtk_window_set_title(GTK_WINDOW(gd_editor_window
), CPrintf(_("GDash Cave Editor - %s")) % caveset
->name
);
586 * edit properties of a reflective object.
588 static void edit_properties_create_window(const char *title
, bool show_cancel
, GtkWidget
*& dialog
, GtkWidget
*¬ebook
) {
589 dialog
=gtk_dialog_new_with_buttons(title
, GTK_WINDOW(gd_editor_window
), GTK_DIALOG_DESTROY_WITH_PARENT
, NULL
);
591 gtk_dialog_add_button(GTK_DIALOG(dialog
), GTK_STOCK_CANCEL
, GTK_RESPONSE_REJECT
);
592 gtk_dialog_add_button(GTK_DIALOG(dialog
), GTK_STOCK_OK
, GTK_RESPONSE_ACCEPT
);
593 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
594 gtk_dialog_set_has_separator(GTK_DIALOG(dialog
), FALSE
);
595 gtk_window_set_default_size(GTK_WINDOW(dialog
), 360, 240);
597 /* tabbed notebook */
598 notebook
=gtk_notebook_new();
599 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook
), GTK_POS_LEFT
);
600 gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook
), TRUE
);
601 gtk_notebook_popup_enable(GTK_NOTEBOOK(notebook
));
602 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), notebook
, TRUE
, TRUE
, 6);
605 static void edit_properties_set_random_max(GtkWidget
*widget
, gpointer data
) {
606 GtkSpinButton
*spin
=GTK_SPIN_BUTTON(widget
);
607 EditorAutoUpdate
*next_eau
=static_cast<EditorAutoUpdate
*>(data
);
608 // the maximum of the next spin button is the current value of this
609 gtk_spin_button_set_range(GTK_SPIN_BUTTON(next_eau
->widget
), 0, gtk_spin_button_get_value_as_int(spin
));
612 static void edit_properties_add_widgets(GtkWidget
*notebook
, std::list
<EditorAutoUpdate
*> &eau_s
, PropertyDescription
const prop_desc
[], Reflective
*str
, Reflective
*def_str
, void (*cave_render_cb
)()) {
613 GtkWidget
*table
=NULL
;
615 EditorAutoUpdate
*previous_random_probability
=0;
617 /* create the entry widgets */
618 for (unsigned i
=0; prop_desc
[i
].identifier
!=NULL
; i
++) {
619 if (prop_desc
[i
].flags
&GD_DONT_SHOW_IN_EDITOR
)
622 if (prop_desc
[i
].type
==GD_TAB
) {
623 /* create a table which will be the widget inside */
624 table
=gtk_table_new(1, 1, FALSE
);
625 gtk_container_set_border_width(GTK_CONTAINER(table
), 12);
626 gtk_table_set_col_spacings(GTK_TABLE(table
), 12);
627 gtk_table_set_row_spacings(GTK_TABLE(table
), 3);
628 /* and to the notebook */
629 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
), table
, gtk_label_new(_(prop_desc
[i
].name
)));
633 g_assert (table
!=NULL
);
635 // name of the setting
636 if (prop_desc
[i
].name
!=0) {
638 if (prop_desc
[i
].type
==GD_LABEL
) // if this is a label, make it bold
639 label
=gd_label_new_leftaligned(CPrintf("<b>%s</b>")%_(prop_desc
[i
].name
));
641 label
=gd_label_new_leftaligned(_(prop_desc
[i
].name
));
642 gtk_table_attach(GTK_TABLE(table
), label
, 0, 1, row
, row
+1,
643 GtkAttachOptions(GTK_FILL
|GTK_SHRINK
),
644 GtkAttachOptions(GTK_FILL
|GTK_SHRINK
), 0, 0);
646 EditorAutoUpdate
*eau
=new EditorAutoUpdate(str
, def_str
, &prop_desc
[i
], cave_render_cb
);
647 gtk_table_attach(GTK_TABLE(table
), eau
->widget
, 1, 2, row
, row
+1,
648 GtkAttachOptions(GTK_FILL
|GTK_EXPAND
),
649 GtkAttachOptions(GTK_FILL
|(eau
->expand_vertically
?GTK_EXPAND
:GTK_SHRINK
)), 0, 0);
650 eau_s
.push_back(eau
);
652 // if this is setting is for a c64 random generator fill, add an extra changed
653 if (prop_desc
[i
].flags
&GD_BD_PROBABILITY
) {
654 if (previous_random_probability
) {
655 g_signal_connect(previous_random_probability
->widget
, "changed", G_CALLBACK(edit_properties_set_random_max
), eau
);
656 // and also update right now
657 gtk_spin_button_set_range(GTK_SPIN_BUTTON(eau
->widget
), 0, gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(previous_random_probability
->widget
)));
659 // and remember this for the next iteration
660 previous_random_probability
=eau
;
667 /* returns true if edited. */
668 /* str is the struct to be edited. */
669 /* def_str is another struct of the same type, which holds default values. */
670 /* show_cancel: true, then a cancel button is also shown. */
672 /* ALWAYS EDITS the object given! It is up to the caller to restore the
673 * state of the object if the response is false. */
674 static bool edit_properties(const char *title
, Reflective
*str
, Reflective
*def_str
, bool show_cancel
, void (*cave_render_cb
)()) {
675 std::list
<EditorAutoUpdate
*> eau_s
;
677 GtkWidget
*dialog
, *notebook
;
678 edit_properties_create_window(title
, show_cancel
, dialog
, notebook
);
680 edit_properties_add_widgets(notebook
, eau_s
, str
->get_description_array(), str
, def_str
, cave_render_cb
);
682 /* running the dialog */
683 gtk_widget_show_all(dialog
);
684 int result
=gtk_dialog_run(GTK_DIALOG(dialog
));
685 gtk_widget_destroy(dialog
);
687 for (std::list
<EditorAutoUpdate
*>::const_iterator it
=eau_s
.begin(); it
!=eau_s
.end(); ++it
)
690 return result
==GTK_RESPONSE_ACCEPT
;
694 /* call edit_properties for a cave, then do some cleanup.
695 * for example, if the size changed, the map has to be resized,
696 * etc. also the user may be warned about resizing the visible area. */
697 static void cave_properties(CaveStored
*cave
, gboolean show_cancel
) {
698 // check if full cave visible
699 bool full_visible
=(cave
->x1
==0 && cave
->y1
==0 && cave
->x2
==cave
->w
-1 && cave
->y2
==cave
->h
-1);
702 // Then later decide what to do.
703 CaveStored
copy(*cave
);
705 bool edited
=edit_properties(_("Cave Properties"), ©
, &def_cave
, show_cancel
, NULL
);
707 // Yes, the user pressed ok - copy the edited version to the original.
709 bool size_changed
=(cave
->w
!=copy
.w
|| cave
->h
!=copy
.h
); // but first remember this
713 caveset
->edited
=true;
715 // if the size changes, we have work to do.
717 // if cave has a map, resize it also
718 if (!cave
->map
.empty())
719 cave
->map
.resize(cave
->w
, cave
->h
, cave
->initial_border
);
721 // if originally the full cave was visible, we set the visible area to the full cave, here again.
728 // do some validation
729 gd_cave_correct_visible_size(*cave
);
731 // check ratios, so they are not larger than number of cells in cave (width*height)
732 PropertyDescription
const *descriptor
=cave
->get_description_array();
733 for (unsigned i
=0; descriptor
[i
].identifier
!=NULL
; i
++)
734 if (descriptor
[i
].flags
&GD_BDCFF_RATIO_TO_CAVE_SIZE
) {
735 std::auto_ptr
<GetterBase
> const &prop
=descriptor
[i
].prop
;
736 switch (descriptor
[i
].type
) {
738 if (cave
->get
<GdInt
>(prop
)>=cave
->w
*cave
->h
)
739 cave
->get
<GdInt
>(prop
)=cave
->w
*cave
->h
;
741 case GD_TYPE_INT_LEVELS
:
742 for (unsigned j
=0; j
<prop
->count
; ++j
)
743 if (cave
->get
<GdIntLevels
>(prop
)[j
]>=cave
->w
*cave
->h
)
744 cave
->get
<GdIntLevels
>(prop
)[j
]=cave
->w
*cave
->h
;
751 /* redraw, recreate, re-everything */
752 select_cave_for_edit(cave
);
754 // if the size of the cave changed, inform the user that he should check the visible region.
755 // also, automatically select the tool.
756 if (size_changed
&& !full_visible
) {
757 select_tool(TOOL_VISIBLE_REGION
);
758 gd_warningmessage(_("You have changed the size of the cave. Please check the size of the visible region!"),
759 _("The visible area of the cave during the game can be smaller than the whole cave. If you resize "
760 "the cave, the area to be shown cannot be guessed automatically. The tool to set this behavior is "
761 "now selected, and shows the current visible area. Use drag and drop to change the position and "
762 "size of the rectangle."));
769 /* edit the properties of the caveset - nothing special. */
770 static void caveset_properties(bool show_cancel
) {
771 CaveSet def_val
; /* to create an instance with default values */
773 CaveSet original
=*caveset
;
774 bool result
=edit_properties(_("Cave Set Properties"), caveset
, &def_val
, true, NULL
);
778 editor_window_set_title();
779 caveset
->edited
=true;
784 /// Select cave edit tool.
785 /// This activates the gtk action in question, so
786 /// toolbar and everything else will be updated
787 static void select_tool(int tool
) {
788 /* find specific object type in action_objects array. */
789 /* (the array indexes and tool integers may not correspond) */
790 for (unsigned i
=0; i
<G_N_ELEMENTS(action_objects
); i
++)
791 if (tool
==action_objects
[i
].value
)
792 gtk_action_activate(gtk_action_group_get_action(actions_edit_tools
, action_objects
[i
].name
));
797 set_status_label_for_cave(CaveStored
*cave
) {
800 name_escaped
=g_markup_escape_text(cave
->name
.c_str(), -1);
801 gtk_label_set_markup(GTK_LABEL (label_object
),
802 CPrintf(_("<b>%s</b>, %s, %dx%d, time %d:%02d, diamonds %d"))
804 % (cave
->selectable
?_("selectable"):_("not selectable"))
807 % (cave
->level_time
[edit_level
]/60)
808 % (cave
->level_time
[edit_level
]%60)
809 % cave
->level_diamonds
[edit_level
]);
810 g_free(name_escaped
);
815 set_status_label_for_caveset_count_caves(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
, gpointer data
) {
819 return FALSE
; /* to continue counting */
823 set_status_label_for_caveset() {
827 if (iconview_cavelist
) {
829 gtk_tree_model_foreach(gtk_icon_view_get_model(GTK_ICON_VIEW(iconview_cavelist
)), (GtkTreeModelForeachFunc
) set_status_label_for_caveset_count_caves
, &count
);
832 count
=caveset
->caves
.size();
834 name_escaped
=g_markup_escape_text(caveset
->name
.c_str(), -1);
835 gtk_label_set_markup(GTK_LABEL (label_object
), CPrintf(_("<b>%s</b>, %d caves")) % name_escaped
% count
);
836 g_free(name_escaped
);
841 render cave - ie. draw it as a map,
842 so it can be presented to the user.
844 static void render_cave() {
845 g_return_if_fail(edited_cave
!=NULL
);
847 /* rendering cave for editor: seed=random, so the user sees which elements are truly random */
848 if (!rendered_cave
) {
849 // if does not exist at all, create now
850 delete rendered_cave
;
851 rendered_cave
=new CaveRendered(*edited_cave
, edit_level
, g_random_int_range(0, GD_CAVE_SEED_MAX
));
854 // otherwise only recreate the map
855 rendered_cave
->create_map(*edited_cave
, edit_level
);
857 /* set size of map; also clear it, as colors might have changed etc. */
858 gfx_buffer
.set_size(rendered_cave
->w
, rendered_cave
->h
, -1);
859 object_highlight_map
.set_size(rendered_cave
->w
, rendered_cave
->h
, false);
861 /* we disable this, so things do not get updated object by object. */
862 /* also we MUST disable this, as the selection change signal would ruin our selected_objects set */
863 object_list_selection_changed_signal_disabled
=TRUE
;
865 /* fill object list store with the objects. */
866 gtk_list_store_clear(object_list
);
869 for(CaveObjectStore::const_iterator it
=edited_cave
->objects
.begin(); it
!=edited_cave
->objects
.end(); ++it
) {
870 CaveObject
*object
=*it
;
871 GdkPixbuf
*element
=NULL
;
873 GdElementEnum characteristic
=object
->get_characteristic_element();
874 if (characteristic
!=O_NONE
)
875 element
=editor_cell_renderer
->combo_pixbuf(characteristic
);
877 const char *levels_stock
;
878 if (object
->is_seen_on_all()) /* if on all levels */
879 levels_stock
=GD_ICON_OBJECT_ON_ALL
;
880 else /* not on all levels... visible on current level? */
881 if (object
->seen_on
[edit_level
])
882 levels_stock
=GD_ICON_OBJECT_NOT_ON_ALL
;
884 levels_stock
=GD_ICON_OBJECT_NOT_ON_CURRENT
;
886 std::string text
=object
->get_coordinates_text();
887 /* use atomic insert with values */
888 GtkTreeIter treeiter
;
889 gtk_list_store_insert_with_values(object_list
, &treeiter
, i
, INDEX_COLUMN
, i
, LEVELS_PIXBUF_COLUMN
, levels_stock
,
890 TYPE_PIXBUF_COLUMN
, action_objects
[object
->type
].stock_id
,
891 ELEMENT_PIXBUF_COLUMN
, element
, TEXT_COLUMN
, text
.c_str(), POINTER_COLUMN
, object
, -1);
893 /* also do selection as now we have the iter in hand */
894 if (selected_objects
.count(object
)!=0)
895 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
)), &treeiter
);
899 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(object_list_tree_view
));
901 /* and now that it is filled, we call the selection changed signal by hand. */
902 object_list_selection_changed_signal_disabled
=FALSE
;
903 object_list_selection_changed_signal(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
)), NULL
);
905 gtk_action_group_set_sensitive (actions_edit_map
, !edited_cave
->map
.empty()); /* map actions when we have map */
906 gtk_action_group_set_sensitive (actions_edit_random
, edited_cave
->map
.empty()); /* random fill actions when no map */
908 /* if no object is selected, show normal cave info */
909 if (selected_objects
.empty())
910 set_status_label_for_cave(edited_cave
);
914 /// Edit properties of an object.
915 static void object_properties(CaveObject
*object
) {
917 g_return_if_fail(object_list_count_selected()==1);
918 object
=object_list_first_selected(); /* select first from list... should be only one selected */
921 std::list
<EditorAutoUpdate
*> eau_s
;
924 CaveObject
*before_edit
=object
->clone();
925 GtkWidget
*dialog
, *notebook
;
926 edit_properties_create_window(_("Object Properties"), true, dialog
, notebook
);
927 edit_properties_add_widgets(notebook
, eau_s
, object
->get_description_array(), object
, before_edit
, render_cave
);
928 gtk_widget_show_all(dialog
);
929 bool changed
=gtk_dialog_run(GTK_DIALOG(dialog
))==GTK_RESPONSE_ACCEPT
;
930 gtk_widget_destroy(dialog
);
934 if (object
->is_invisible()) {
935 object
->enable_on_all();
937 gd_warningmessage(_("The object should be visible on at least one level."), _("Enabled this object on all levels."));
941 undo_do_one_step_but_no_redo();
943 for (std::list
<EditorAutoUpdate
*>::const_iterator it
=eau_s
.begin(); it
!=eau_s
.end(); ++it
)
948 /* this is the same scroll routine as the one used for the game. only the parameters are changed a bit. */
949 static void drawcave_timeout_scroll(int player_x
, int player_y
) {
950 static int scroll_desired_x
=0, scroll_desired_y
=0;
951 GtkAdjustment
*adjustment
;
952 int scroll_center_x
, scroll_center_y
;
954 /* hystheresis size is this, multiplied by two. */
955 int cs
=editor_cell_renderer
->get_cell_size();
956 int scroll_start_x
=scroll_window
->allocation
.width
/2-2*cs
;
957 int scroll_start_y
=scroll_window
->allocation
.height
/2-2*cs
;
958 int scroll_speed
=cs
/4;
960 /* get the size of the window so we know where to place player.
961 * first guess is the middle of the screen.
962 * drawing_area->parent->parent is the viewport.
963 * +cellsize/2 gets the stomach of player :) so the very center */
964 scroll_center_x
=player_x
* cs
+ cs
/2 - drawing_area
->parent
->parent
->allocation
.width
/2;
965 scroll_center_y
=player_y
* cs
+ cs
/2 - drawing_area
->parent
->parent
->allocation
.height
/2;
968 /* hystheresis function.
969 * when scrolling left, always go a bit less left than player being at the middle.
970 * when scrolling right, always go a bit less to the right. */
971 adjustment
=gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll_window
));
972 if (adjustment
->value
+scroll_start_x
<scroll_center_x
)
973 scroll_desired_x
=scroll_center_x
-scroll_start_x
;
974 if (adjustment
->value
-scroll_start_x
>scroll_center_x
)
975 scroll_desired_x
=scroll_center_x
+scroll_start_x
;
977 scroll_desired_x
=CLAMP(scroll_desired_x
, 0, adjustment
->upper
- adjustment
->step_increment
- adjustment
->page_increment
);
978 if (adjustment
->value
<scroll_desired_x
) {
979 for (i
=0; i
<scroll_speed
; i
++)
980 if (adjustment
->value
<scroll_desired_x
)
982 gtk_adjustment_value_changed(adjustment
);
984 if (adjustment
->value
>scroll_desired_x
) {
985 for (i
=0; i
<scroll_speed
; i
++)
986 if (adjustment
->value
>scroll_desired_x
)
988 gtk_adjustment_value_changed(adjustment
);
992 adjustment
=gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll_window
));
993 if (adjustment
->value
+scroll_start_y
<scroll_center_y
)
994 scroll_desired_y
=scroll_center_y
-scroll_start_y
;
995 if (adjustment
->value
-scroll_start_y
>scroll_center_y
)
996 scroll_desired_y
=scroll_center_y
+scroll_start_y
;
998 scroll_desired_y
=CLAMP(scroll_desired_y
, 0, adjustment
->upper
- adjustment
->step_increment
- adjustment
->page_increment
);
999 if (adjustment
->value
<scroll_desired_y
) {
1000 for (i
=0; i
<scroll_speed
; i
++)
1001 if (adjustment
->value
<scroll_desired_y
)
1002 adjustment
->value
++;
1003 gtk_adjustment_value_changed(adjustment
);
1005 if (adjustment
->value
>scroll_desired_y
) {
1006 for (i
=0; i
<scroll_speed
; i
++)
1007 if (adjustment
->value
>scroll_desired_y
)
1008 adjustment
->value
--;
1009 gtk_adjustment_value_changed(adjustment
);
1014 /* timeout 'interrupt', drawing cave in cave editor. */
1015 static gboolean
drawing_area_draw_timeout(gpointer data
) {
1016 static int animcycle
=0;
1017 static bool player_blinking
=0;
1018 static bool hand_cursor
= false;
1020 bool editor_window_is_sensitive
= gtk_widget_get_sensitive(gd_editor_window
);
1022 /* if nothing to draw or nowhere to draw :) exit.
1023 * this is necessary as the interrupt is not uninstalled when the selector is running. */
1024 if (!drawing_area
|| !rendered_cave
)
1027 g_return_val_if_fail(!gfx_buffer
.empty(), TRUE
);
1029 /* when mouse over a drawing object, cursor changes */
1030 if (mouse_x
>= 0 && mouse_y
>= 0) {
1031 bool new_hand_cursor
= rendered_cave
->objects_order(mouse_x
, mouse_y
)!=NULL
;
1033 if (hand_cursor
!=new_hand_cursor
) {
1034 hand_cursor
= new_hand_cursor
;
1035 gdk_window_set_cursor(drawing_area
->window
, hand_cursor
?gdk_cursor_new(GDK_HAND1
):NULL
);
1039 /* only do cell animations when window is active.
1040 * otherwise... user is testing the cave, animation would just waste cpu. */
1041 if (gtk_window_has_toplevel_focus(GTK_WINDOW(gd_editor_window
)))
1042 animcycle
=(animcycle
+1) & 7;
1044 if (animcycle
==0) /* player blinking is started at the beginning of animation sequences. */
1045 player_blinking
=g_random_int_range(0, 4)==0; /* 1/4 chance of blinking, every sequence. */
1047 int cs
= editor_cell_renderer
->get_cell_size();
1049 for (int y
=0; y
<rendered_cave
->h
; y
++) {
1050 for (int x
=0; x
<rendered_cave
->w
; x
++) {
1054 draw
=gd_element_properties
[rendered_cave
->map(x
, y
)].image_simple
;
1056 draw
=gd_element_properties
[rendered_cave
->map(x
, y
)].image
;
1057 /* special case is player - sometimes blinking :) */
1058 if (player_blinking
&& (rendered_cave
->map(x
, y
)==O_INBOX
))
1059 draw
=gd_element_properties
[O_PLAYER_BLINK
].image_simple
;
1060 /* the biter switch also shows its state */
1061 if (rendered_cave
->map(x
, y
)==O_BITER_SWITCH
)
1062 draw
=gd_element_properties
[O_BITER_SWITCH
].image_simple
+rendered_cave
->biter_delay_frame
;
1064 /* negative value means animation */
1066 draw
=-draw
+animcycle
;
1068 /* object coloring */
1069 if (editor_window_is_sensitive
) {
1070 /* if the editor is active */
1071 if (action
==TOOL_VISIBLE_REGION
) {
1072 /* if showing visible region, different color applies for: */
1073 if (x
>=rendered_cave
->x1
&& x
<=rendered_cave
->x2
&& y
>=rendered_cave
->y1
&& y
<=rendered_cave
->y2
)
1075 if (x
==rendered_cave
->x1
|| x
==rendered_cave
->x2
|| y
==rendered_cave
->y1
|| y
==rendered_cave
->y2
)
1076 draw
+=NUM_OF_CELLS
; /* once again */
1078 if (object_highlight_map(x
,y
)) /* if it is a selected object, make it colored */
1079 draw
+=2*NUM_OF_CELLS
;
1080 else if (gd_colored_objects
&& rendered_cave
->objects_order(x
, y
)!=0)
1081 /* if it belongs to any other element, make it colored a bit */
1085 /* if the editor is inactive */
1086 draw
+= NUM_OF_CELLS
;
1089 /* the drawing itself */
1090 if (gfx_buffer(x
,y
)!=draw
) {
1091 screen
->blit(editor_cell_renderer
->cell(draw
), x
*cs
, y
*cs
);
1092 gfx_buffer(x
, y
)=draw
;
1100 /* draw a mark for fill objects */
1101 for (int y
=0; y
<rendered_cave
->h
; y
++) {
1102 for (int x
=0; x
<rendered_cave
->w
; x
++) {
1103 /* for fill objects, we show their origin */
1104 if (object_highlight_map(x
, y
)
1105 && (rendered_cave
->objects_order(x
, y
)->type
==CaveObject::GD_FLOODFILL_BORDER
1106 || rendered_cave
->objects_order(x
, y
)->type
==CaveObject::GD_FLOODFILL_REPLACE
)) {
1107 Coordinate c
= dynamic_cast<CaveFill
&>(*rendered_cave
->objects_order(x
, y
)).get_start_coordinate();
1108 /* if this one is the starting coordinate, mark it */
1109 if (c
.x
== x
&& c
.y
== y
) {
1110 gdk_draw_rectangle(drawing_area
->window
, drawing_area
->style
->white_gc
, FALSE
, c
.x
*cs
, c
.y
*cs
, cs
-1, cs
-1);
1111 gdk_draw_line(drawing_area
->window
, drawing_area
->style
->white_gc
, c
.x
*cs
, c
.y
*cs
, (c
.x
+1)*cs
-1, (c
.y
+1)*cs
-1);
1112 gdk_draw_line(drawing_area
->window
, drawing_area
->style
->white_gc
, c
.x
*cs
, (c
.y
+1)*cs
-1, (c
.x
+1)*cs
-1, c
.y
*cs
);
1113 gfx_buffer(c
.x
, c
.y
)=-1; /* so it will always be redrawn */
1119 /* draw a mark for the mouse pointer */
1120 if (mouse_x
>=0 && mouse_y
>=0) {
1121 /* this is the cell the mouse is over */
1122 gdk_draw_rectangle(drawing_area
->window
, drawing_area
->style
->white_gc
, FALSE
, mouse_x
*cs
, mouse_y
*cs
, cs
-1, cs
-1);
1123 /* always redraw this cell the next frame - the easiest way to always get rid of the rectangle when the mouse is moved */
1124 gfx_buffer(mouse_x
, mouse_y
)=-1;
1127 /* automatic scrolling */
1128 if (mouse_x
>=0 && mouse_y
>=0 && button1_clicked
)
1129 drawcave_timeout_scroll(mouse_x
, mouse_y
);
1136 * cave drawing area expose event.
1139 drawing_area_expose_event(GtkWidget
*widget
, GdkEventExpose
*event
, gpointer data
) {
1140 if (!widget
->window
|| gfx_buffer
.empty() || rendered_cave
==NULL
)
1142 int cs
=editor_cell_renderer
->get_cell_size();
1144 /* calculate which area to update */
1145 int x1
= event
->area
.x
/cs
;
1146 int y1
= event
->area
.y
/cs
;
1147 int x2
= (event
->area
.x
+event
->area
.width
-1)/cs
;
1148 int y2
= (event
->area
.y
+event
->area
.height
-1)/cs
;
1149 /* when resizing the cave, we may get events which store the old size, if the drawing area is not yet resized. */
1152 if (x2
>=rendered_cave
->w
) x2
=rendered_cave
->w
-1;
1153 if (y2
>=rendered_cave
->h
) y2
=rendered_cave
->h
-1;
1155 /* tag them as "to be redrawn" */
1156 for (int y
=y1
; y
<=y2
; y
++)
1157 for (int x
=x1
; x
<=x2
; x
++)
1158 gfx_buffer(x
, y
) = -1;
1163 static void drawing_area_destroyed(GtkWidget
*widget
, gpointer data
) {
1166 drawing_area
= NULL
;
1170 /***************************************************
1178 edited_cave_add_object(CaveObject
*object
) {
1181 /* only visible on level... */
1182 int act
=gtk_combo_box_get_active(GTK_COMBO_BOX(new_object_level_combo
));
1183 int lev
=new_objects_visible_on
[act
].switch_to_level
;
1184 for (int i
=0; i
<lev
; ++i
)
1185 object
->seen_on
[i
]=false;
1187 edited_cave
->push_back_adopt(object
);
1188 render_cave(); /* new object created, so re-render cave */
1189 object_list_select_one_object(edited_cave
->objects
.back()); /* also make it selected; so it can be edited further */
1193 /* mouse button press event */
1195 drawing_area_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
) {
1196 g_return_val_if_fail (edited_cave
!=NULL
, FALSE
);
1198 /* right click opens popup */
1199 if (event
->button
==3) {
1200 gtk_menu_popup(GTK_MENU(drawing_area_popup
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
1204 /* this should be also false for doubleclick! so we do not do if (event->tye....) */
1205 button1_clicked
= event
->type
==GDK_BUTTON_PRESS
&& event
->button
==1;
1207 int cs
= editor_cell_renderer
->get_cell_size();
1208 clicked_x
=((int) event
->x
)/cs
;
1209 clicked_y
=((int) event
->y
)/cs
;
1210 /* middle button picks element from screen */
1211 if (event
->button
==2) {
1212 if (event
->state
& GDK_CONTROL_MASK
)
1213 gd_element_button_set(fillelement_button
, GdElementEnum(rendered_cave
->map( clicked_x
, clicked_y
)));
1215 gd_element_button_set(element_button
, GdElementEnum(rendered_cave
->map( clicked_x
, clicked_y
)));
1219 /* we do not handle anything other than buttons3,2 above, and button 1 */
1220 if (event
->button
!=1)
1223 /* if double click, open element properties window.
1224 * if no element selected, open cave properties window.
1225 * (if mouse if over an element, the first click selected it.) */
1226 /* do not allow this doubleclick for visible region mode as that one does not select objects */
1227 if (event
->type
==GDK_2BUTTON_PRESS
&& action
!=TOOL_VISIBLE_REGION
) {
1228 if (rendered_cave
->objects_order(clicked_x
, clicked_y
))
1229 object_properties(rendered_cave
->objects_order(clicked_x
, clicked_y
));
1231 cave_properties(edited_cave
, TRUE
);
1237 /* action=move: now the user is selecting an object by the mouse click */
1238 if ((event
->state
& GDK_CONTROL_MASK
) || (event
->state
& GDK_SHIFT_MASK
)) {
1239 CaveObject
*clicked_on_object
=rendered_cave
->objects_order(clicked_x
, clicked_y
);
1240 /* CONTROL or SHIFT PRESSED: multiple selection */
1241 /* check if clicked on an object. */
1242 if (clicked_on_object
!=0) {
1243 if (object_list_is_selected(clicked_on_object
))
1244 object_list_remove_from_selection(clicked_on_object
);
1246 object_list_add_to_selection(clicked_on_object
);
1249 CaveObject
*clicked_on_object
=rendered_cave
->objects_order(clicked_x
, clicked_y
);
1250 /* CONTROL NOT PRESSED: single selection */
1251 /* if the object clicked is not currently selected, we select it. if it is, do nothing, so a multiple selection remains. */
1252 if (clicked_on_object
) {
1253 /* check if currently selected. if yes, do nothing, as it would make multi-object drag&drops impossible. */
1254 if (!object_list_is_selected(clicked_on_object
))
1255 object_list_select_one_object(clicked_on_object
);
1257 /* if clicking on a non-object, deselect all */
1258 object_list_clear_selection();
1261 /* prepare for undo */
1262 undo_move_flag
=FALSE
;
1266 /* freehand tool: draw points in each place. */
1267 /* if already the same element there, which is placed by an object, skip! */
1268 if (rendered_cave
->map(clicked_x
, clicked_y
)!=gd_element_button_get(element_button
) || rendered_cave
->objects_order(clicked_x
, clicked_y
)==NULL
)
1269 /* it places a point. and later, when dragging the mouse, it may place another points. */
1270 edited_cave_add_object(new CavePoint(Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
)));
1273 case TOOL_VISIBLE_REGION
:
1274 /* new click... prepare for undo! */
1275 undo_move_flag
=FALSE
;
1276 /* do nothing, the motion event will matter */
1280 edited_cave_add_object(new CavePoint(Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
)));
1284 edited_cave_add_object(new CaveLine(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
)));
1287 case TOOL_RECTANGLE
:
1288 edited_cave_add_object(new CaveRectangle(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
)));
1291 case TOOL_FILLED_RECTANGLE
:
1292 edited_cave_add_object(new CaveFillRect(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
), gd_element_button_get(fillelement_button
)));
1296 edited_cave_add_object(new CaveRaster(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), Coordinate(2, 2), gd_element_button_get(element_button
)));
1300 edited_cave_add_object(new CaveJoin(Coordinate(0, 0), gd_element_button_get(element_button
), gd_element_button_get(fillelement_button
)));
1303 case TOOL_FLOODFILL_REPLACE
:
1304 edited_cave_add_object(new CaveFloodFill(Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
), rendered_cave
->map(clicked_x
, clicked_y
)));
1307 case TOOL_FLOODFILL_BORDER
:
1308 edited_cave_add_object(new CaveBoundaryFill(Coordinate(clicked_x
, clicked_y
), gd_element_button_get(fillelement_button
), gd_element_button_get(element_button
)));
1312 edited_cave_add_object(new CaveMaze(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
), gd_element_button_get(fillelement_button
), CaveMaze::Perfect
));
1315 case TOOL_MAZE_UNICURSAL
:
1316 edited_cave_add_object(new CaveMaze(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
), gd_element_button_get(fillelement_button
), CaveMaze::Unicursal
));
1319 case TOOL_MAZE_BRAID
:
1320 edited_cave_add_object(new CaveMaze(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), gd_element_button_get(element_button
), gd_element_button_get(fillelement_button
), CaveMaze::Braid
));
1323 case TOOL_RANDOM_FILL
:
1324 edited_cave_add_object(new CaveRandomFill(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
)));
1327 case TOOL_COPY_PASTE
:
1328 edited_cave_add_object(new CaveCopyPaste(Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
), Coordinate(clicked_x
, clicked_y
)));
1331 g_assert_not_reached();
1337 drawing_area_button_release_event(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
) {
1338 if (event
->type
==GDK_BUTTON_RELEASE
&& event
->button
==1)
1339 button1_clicked
=FALSE
;
1343 /* mouse leaves drawing area event */
1345 drawing_area_leave_event(GtkWidget
*widget
, GdkEventCrossing
* event
, gpointer data
) {
1346 /* do not check if it as enter event, as we did not connect that one. */
1347 gtk_label_set_text (GTK_LABEL (label_coordinate
), "[x: y: ]");
1353 /* mouse motion event */
1355 drawing_area_motion_event(GtkWidget
*widget
, GdkEventMotion
*event
, gpointer data
) {
1357 GdkModifierType state
;
1359 /* just to be sure. */
1364 gdk_window_get_pointer (event
->window
, &x
, &y
, &state
);
1368 state
=GdkModifierType(event
->state
);
1371 int cs
=editor_cell_renderer
->get_cell_size();
1375 /* if button1 not pressed, remember this. we also use the motion event to see if it. hackish. */
1376 if (!(state
&GDK_BUTTON1_MASK
))
1377 button1_clicked
=FALSE
;
1379 /* check if event coordinates inside drawing area. when holding the mouse
1380 * button, gdk can send coordinates outside! */
1381 if (x
<0 || y
<0 || x
>=rendered_cave
->w
|| y
>=rendered_cave
->h
)
1384 /* check if mouse has moved to another cell. also set label showing coordinate. */
1385 if (mouse_x
!=x
|| mouse_y
!=y
) {
1388 gtk_label_set_markup(GTK_LABEL (label_coordinate
), CPrintf("[x:%d y:%d]") % x
% y
);
1391 /* if we do not remember button 1 press, then don't do anything. */
1392 /* this solves some misinterpretation of mouse events, when windows appear or mouse pointer exits the drawing area and enters again */
1393 if (!(state
&GDK_BUTTON1_MASK
) || !button1_clicked
)
1398 /* if the mouse pointer did not move at least one cell in x or y direction, return */
1402 /* changing visible region is different; independent of cave objects. */
1403 if (action
==TOOL_VISIBLE_REGION
) {
1404 /* save visible region flag only once */
1405 if (undo_move_flag
==FALSE
) {
1407 undo_move_flag
=TRUE
;
1410 /* try to drag (x1;y1) corner. */
1411 if (clicked_x
==edited_cave
->x1
&& clicked_y
==edited_cave
->y1
) {
1412 edited_cave
->x1
+=dx
;
1413 edited_cave
->y1
+=dy
;
1416 /* try to drag (x2;y1) corner. */
1417 if (clicked_x
==edited_cave
->x2
&& clicked_y
==edited_cave
->y1
) {
1418 edited_cave
->x2
+=dx
;
1419 edited_cave
->y1
+=dy
;
1422 /* try to drag (x1;y2) corner. */
1423 if (clicked_x
==edited_cave
->x1
&& clicked_y
==edited_cave
->y2
) {
1424 edited_cave
->x1
+=dx
;
1425 edited_cave
->y2
+=dy
;
1428 /* try to drag (x2;y2) corner. */
1429 if (clicked_x
==edited_cave
->x2
&& clicked_y
==edited_cave
->y2
) {
1430 edited_cave
->x2
+=dx
;
1431 edited_cave
->y2
+=dy
;
1434 /* drag the whole */
1435 edited_cave
->x1
+=dx
;
1436 edited_cave
->y1
+=dy
;
1437 edited_cave
->x2
+=dx
;
1438 edited_cave
->y2
+=dy
;
1443 /* check and adjust ranges if necessary */
1444 gd_cave_correct_visible_size(*edited_cave
);
1446 /* instead of re-rendering the cave, we just copy the changed values. */
1447 rendered_cave
->x1
=edited_cave
->x1
;
1448 rendered_cave
->x2
=edited_cave
->x2
;
1449 rendered_cave
->y1
=edited_cave
->y1
;
1450 rendered_cave
->y2
=edited_cave
->y2
;
1454 if (action
==TOOL_FREEHAND
) {
1455 /* the freehand tool is different a bit. it draws single points automatically */
1456 /* but only to places where there is no such object already. */
1457 if (rendered_cave
->map(x
, y
)!=gd_element_button_get (element_button
) || rendered_cave
->objects_order(x
, y
)==NULL
) {
1458 edited_cave
->push_back_adopt(new CavePoint(Coordinate(x
, y
), gd_element_button_get(element_button
)));
1459 render_cave(); /* we do this here by hand; do not use changed flag; otherwise object_list_add_to_selection wouldn't work */
1460 object_list_add_to_selection(edited_cave
->objects
.back()); /* this way all points will be selected together when using freehand */
1465 if (!object_list_is_any_selected())
1468 if (object_list_count_selected()==1) {
1469 /* MOVING, DRAGGING A SINGLE OBJECT **************************/
1470 CaveObject
*object
=object_list_first_selected();
1474 /* MOVING AN EXISTING OBJECT */
1475 if (undo_move_flag
==FALSE
) {
1477 undo_move_flag
=TRUE
;
1480 object
->move(Coordinate(clicked_x
, clicked_y
), Coordinate(dx
, dy
));
1483 /* DRAGGING THE MOUSE, WHEN THE OBJECT WAS JUST CREATED */
1485 case TOOL_FLOODFILL_BORDER
:
1486 case TOOL_FLOODFILL_REPLACE
:
1488 case TOOL_RECTANGLE
:
1489 case TOOL_FILLED_RECTANGLE
:
1492 case TOOL_MAZE_UNICURSAL
:
1493 case TOOL_MAZE_BRAID
:
1494 case TOOL_RANDOM_FILL
:
1495 case TOOL_COPY_PASTE
:
1497 object
->create_drag(Coordinate(x
, y
), Coordinate(dx
, dy
));
1500 g_assert_not_reached();
1503 if (object_list_count_selected()>1 && action
==TOOL_MOVE
) {
1504 /* MOVING MULTIPLE OBJECTS */
1505 if (undo_move_flag
==FALSE
) {
1507 undo_move_flag
=TRUE
;
1510 for (std::set
<CaveObject
*>::iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
)
1511 (*it
)->move(Coordinate(dx
, dy
));
1521 /****************************************************/
1524 /* to remember size of window */
1526 editor_window_configure_event(GtkWidget
*widget
, GdkEventConfigure
*event
, gpointer data
) {
1527 gd_editor_window_width
= event
->width
;
1528 gd_editor_window_height
= event
->height
;
1533 /* destroy editor window - do some cleanups */
1535 editor_window_destroy_event(GtkWidget
*widget
, gpointer data
) {
1536 /* remove drawing interrupt. */
1537 g_source_remove(timeout_id
);
1538 /* if cave is drawn, free. */
1539 delete rendered_cave
;
1540 rendered_cave
= NULL
;
1542 delete editor_cell_renderer
;
1543 editor_cell_renderer
= NULL
;
1544 delete editor_pixbuf_factory
;
1545 editor_pixbuf_factory
= NULL
;
1547 /* we destroy the icon view explicitly. so the caveset gets recreated... gd_main_stop_game will need that, as it checks all caves for replay. */
1548 if (iconview_cavelist
)
1549 gtk_widget_destroy(iconview_cavelist
);
1551 g_hash_table_destroy(cave_pixbufs
);
1556 editor_window_delete_event(GtkWidget
*widget
, GdkEventAny
*event
, gpointer data
) {
1562 /****************************************************
1564 * CAVE SELECTOR ICON VIEW
1570 icon_view_update_pixbufs_timeout(gpointer data
) {
1572 GtkTreeModel
*model
;
1577 /* if no icon view found, remove interrupt. */
1578 if (!iconview_cavelist
)
1581 model
=gtk_icon_view_get_model(GTK_ICON_VIEW(iconview_cavelist
));
1582 path
=gtk_tree_path_new_first();
1585 /* render a maximum of 5 pixbufs at a time */
1586 while (created
<5 && (finish
=gtk_tree_model_get_iter (model
, &iter
, path
))) {
1588 GdkPixbuf
*pixbuf
, *pixbuf_in_icon_view
;
1590 gtk_tree_model_get(model
, &iter
, CAVE_COLUMN
, &cave
, PIXBUF_COLUMN
, &pixbuf_in_icon_view
, -1);
1591 pixbuf
=(GdkPixbuf
*) g_hash_table_lookup(cave_pixbufs
, cave
);
1593 /* if we have no pixbuf, generate one. */
1595 pixbuf_in_icon_view
=NULL
; /* to force update below */
1596 CaveRendered
*rendered
=new CaveRendered(*cave
, 0, 0); /* render at level 1, seed=0 */
1597 pixbuf
=gd_drawcave_to_pixbuf(rendered
, *editor_cell_renderer
, 128, 128, true, true); /* draw 128x128 icons at max */
1598 if (!cave
->selectable
) {
1599 GdkPixbuf
*colored
=gdk_pixbuf_composite_color_simple(pixbuf
, gdk_pixbuf_get_width(pixbuf
), gdk_pixbuf_get_height(pixbuf
), GDK_INTERP_NEAREST
, 160, 1, gd_flash_color
.get_uint(), gd_flash_color
.get_uint());
1600 g_object_unref(pixbuf
); /* forget original */
1604 g_hash_table_insert(cave_pixbufs
, cave
, pixbuf
);
1606 created
++; /* created at least one, it took time */
1609 /* if generated a new pixbuf, or the icon view does not contain the pixbuf: */
1610 if (pixbuf
!=pixbuf_in_icon_view
)
1611 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, PIXBUF_COLUMN
, pixbuf
, -1);
1613 gtk_tree_path_next (path
);
1615 gtk_tree_path_free (path
);
1621 icon_view_update_pixbufs() {
1622 g_timeout_add_full(G_PRIORITY_LOW
, 10, icon_view_update_pixbufs_timeout
, NULL
, NULL
);
1626 /* this is also called as an item activated signal. */
1627 /* so we do not use its parameters. */
1629 icon_view_edit_cave_cb() {
1632 GtkTreeModel
*model
;
1635 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW(iconview_cavelist
));
1636 g_return_if_fail (list
!=NULL
);
1638 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (iconview_cavelist
));
1639 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) list
->data
);
1640 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1641 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
); /* free the list of paths */
1643 select_cave_for_edit (cave
);
1644 gtk_combo_box_set_active(GTK_COMBO_BOX(new_object_level_combo
), 0); /* always default to level 1 */
1648 icon_view_rename_cave_cb(GtkWidget
*widget
, gpointer data
) {
1651 GtkTreeModel
*model
;
1653 GtkWidget
*dialog
, *entry
;
1656 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW(iconview_cavelist
));
1657 g_return_if_fail (list
!=NULL
);
1659 /* use first element, as icon view is configured to enable only one selection */
1660 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (iconview_cavelist
));
1661 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) list
->data
);
1662 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1663 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
); /* free the list of paths */
1666 dialog
=gtk_dialog_new_with_buttons(_("Cave Name"), GTK_WINDOW(gd_editor_window
), GtkDialogFlags(GTK_DIALOG_NO_SEPARATOR
|GTK_DIALOG_DESTROY_WITH_PARENT
),
1667 GTK_STOCK_CANCEL
, GTK_RESPONSE_REJECT
, GTK_STOCK_OK
, GTK_RESPONSE_ACCEPT
, NULL
);
1668 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
1669 entry
=gtk_entry_new();
1670 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
1671 gtk_entry_set_text(GTK_ENTRY(entry
), cave
->name
.c_str());
1672 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), entry
, FALSE
, FALSE
, 6);
1674 gtk_widget_show(entry
);
1675 result
=gtk_dialog_run(GTK_DIALOG(dialog
));
1676 if (result
==GTK_RESPONSE_ACCEPT
) {
1677 cave
->name
=gtk_entry_get_text(GTK_ENTRY(entry
));
1678 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
, NAME_COLUMN
, cave
->name
.c_str(), -1);
1680 gtk_widget_destroy(dialog
);
1684 icon_view_cave_make_selectable_cb(GtkWidget
*widget
, gpointer data
) {
1687 GtkTreeModel
*model
;
1690 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW(iconview_cavelist
));
1691 g_return_if_fail (list
!=NULL
);
1693 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (iconview_cavelist
));
1694 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) list
->data
);
1695 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1696 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
); /* free the list of paths */
1698 if (!cave
->selectable
) {
1699 cave
->selectable
=TRUE
;
1700 /* we remove its pixbuf, as its color will be different */
1701 g_hash_table_remove(cave_pixbufs
, cave
);
1703 icon_view_update_pixbufs();
1707 icon_view_cave_make_unselectable_cb(GtkWidget
*widget
, gpointer data
) {
1710 GtkTreeModel
*model
;
1713 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW(iconview_cavelist
));
1714 g_return_if_fail (list
!=NULL
);
1716 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (iconview_cavelist
));
1717 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) list
->data
);
1718 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1719 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
); /* free the list of paths */
1721 if (cave
->selectable
) {
1722 cave
->selectable
=FALSE
;
1723 /* we remove its pixbuf, as its color will be different */
1724 g_hash_table_remove(cave_pixbufs
, cave
);
1726 icon_view_update_pixbufs();
1730 icon_view_selection_changed_cb(GtkWidget
*widget
, gpointer data
) {
1732 GtkTreeModel
*model
;
1735 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW (widget
));
1736 count
=g_list_length(list
);
1737 gtk_action_group_set_sensitive(actions_cave_selector
, count
==1);
1738 gtk_action_group_set_sensitive(actions_clipboard
, count
!=0);
1740 set_status_label_for_caveset();
1746 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (widget
));
1748 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) list
->data
);
1749 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1751 set_status_label_for_cave(cave
); /* status bar now shows some basic data for cave */
1754 gtk_label_set_markup(GTK_LABEL (label_object
), CPrintf(_("%d caves selected")) % count
);
1755 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
1760 /* for caveset icon view */
1762 icon_view_destroyed (GtkIconView
* icon_view
, gpointer data
) {
1764 GtkTreeModel
*model
;
1767 /* caveset should be an empty list, as the icon view stores the current caves and order */
1768 g_assert(caveset
->caves
.empty());
1770 model
=gtk_icon_view_get_model(icon_view
);
1771 path
=gtk_tree_path_new_first();
1772 while (gtk_tree_model_get_iter (model
, &iter
, path
)) {
1775 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
1776 /* make a new list from the new order obtained from the icon view */
1777 caveset
->caves
.push_back_adopt(cave
);
1779 gtk_tree_path_next (path
);
1781 gtk_tree_path_free (path
);
1787 icon_view_add_cave(GtkListStore
*store
, CaveStored
*cave
) {
1788 GtkTreeIter treeiter
;
1789 GdkPixbuf
*cave_pixbuf
;
1790 static GdkPixbuf
*missing_image
=NULL
;
1792 if (!missing_image
) {
1793 missing_image
=gtk_widget_render_icon(gd_editor_window
, GTK_STOCK_MISSING_IMAGE
, GTK_ICON_SIZE_DIALOG
, NULL
);
1796 /* if we already know the pixbuf, set it. */
1797 cave_pixbuf
=(GdkPixbuf
*) g_hash_table_lookup(cave_pixbufs
, cave
);
1798 if (cave_pixbuf
==NULL
)
1799 cave_pixbuf
=missing_image
;
1800 gtk_list_store_insert_with_values (store
, &treeiter
, -1, CAVE_COLUMN
, cave
, NAME_COLUMN
, cave
->name
.c_str(), PIXBUF_COLUMN
, cave_pixbuf
, -1);
1803 /* does nothing else but sets caveset_edited to true. called by "reordering" (drag&drop), which is implemented by gtk+ by inserting and deleting */
1804 /* we only connect this signal after adding all caves to the icon view, so it is only activated by the user! */
1806 icon_view_row_inserted(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
, gpointer data
) {
1807 caveset
->edited
=TRUE
;
1810 /* for popup menu, by properties key */
1812 icon_view_popup_menu(GtkWidget
*widget
, gpointer data
) {
1813 gtk_menu_popup(GTK_MENU(caveset_popup
), NULL
, NULL
, NULL
, NULL
, 0, gtk_get_current_event_time());
1816 /* for popup menu, by right-click */
1818 icon_view_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
, gpointer data
) {
1819 if (event
->type
==GDK_BUTTON_PRESS
&& event
->button
==3) {
1820 gtk_menu_popup(GTK_MENU(caveset_popup
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
1827 * selects a cave for edit.
1828 * if given a cave, creates a drawing area, shows toolbars...
1829 * if given no cave, creates a gtk icon view for a game overview.
1833 select_cave_for_edit(CaveStored
*cave
) {
1834 object_list_clear_selection();
1836 gtk_action_group_set_sensitive (actions_edit_object
, FALSE
); /* will be enabled later if needed */
1837 gtk_action_group_set_sensitive (actions_edit_one_object
, FALSE
); /* will be enabled later if needed */
1838 gtk_action_group_set_sensitive (actions_edit_cave
, cave
!=NULL
);
1839 gtk_action_group_set_sensitive (actions_edit_caveset
, cave
==NULL
);
1840 gtk_action_group_set_sensitive (actions_edit_tools
, cave
!=NULL
);
1841 gtk_action_group_set_sensitive (actions_edit_map
, FALSE
); /* will be enabled later if needed */
1842 gtk_action_group_set_sensitive (actions_edit_random
, FALSE
); /* will be enabled later if needed */
1843 gtk_action_group_set_sensitive (actions_toggle
, cave
!=NULL
);
1844 /* this is sensitized by an icon selector callback. */
1845 gtk_action_group_set_sensitive (actions_cave_selector
, FALSE
); /* will be enabled later if needed */
1846 gtk_action_group_set_sensitive (actions_clipboard
, FALSE
); /* will be enabled later if needed */
1847 gtk_action_group_set_sensitive (actions_clipboard_paste
,
1848 (cave
!=NULL
&& !object_clipboard
.empty())
1849 || (cave
==NULL
&& !cave_clipboard
.empty()));
1850 gtk_action_group_set_sensitive (actions_edit_undo
, cave
!=NULL
&& !undo_caves
.empty());
1851 gtk_action_group_set_sensitive (actions_edit_redo
, cave
!=NULL
&& !redo_caves
.empty());
1856 /* if cave data given, show it. */
1858 if (iconview_cavelist
)
1859 gtk_widget_destroy(iconview_cavelist
);
1861 if (gd_show_object_list
)
1862 gtk_widget_show (scroll_window_objects
);
1864 /* create pixbufs for these colors */
1865 editor_cell_renderer
->select_pixbuf_colors(edited_cave
->color0
, edited_cave
->color1
, edited_cave
->color2
, edited_cave
->color3
, edited_cave
->color4
, edited_cave
->color5
);
1866 gd_element_button_update_pixbuf(element_button
);
1867 gd_element_button_update_pixbuf(fillelement_button
);
1869 /* put drawing area in an alignment, so window can be any large w/o problems */
1870 if (!drawing_area
) {
1871 GtkWidget
*align
=gtk_alignment_new(0.5, 0.5, 0, 0);
1872 gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll_window
), align
);
1873 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll_window
), GTK_SHADOW_NONE
);
1875 drawing_area
= gtk_drawing_area_new();
1876 screen
= new GTKScreen(drawing_area
);
1878 /* enable some events */
1879 gtk_widget_add_events(drawing_area
, GDK_EXPOSURE_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_LEAVE_NOTIFY_MASK
);
1880 g_signal_connect(G_OBJECT(drawing_area
), "destroy", G_CALLBACK(drawing_area_destroyed
), NULL
);
1881 g_signal_connect(G_OBJECT(drawing_area
), "button_press_event", G_CALLBACK(drawing_area_button_press_event
), NULL
);
1882 g_signal_connect(G_OBJECT(drawing_area
), "button_release_event", G_CALLBACK(drawing_area_button_release_event
), NULL
);
1883 g_signal_connect(G_OBJECT(drawing_area
), "motion_notify_event", G_CALLBACK(drawing_area_motion_event
), NULL
);
1884 g_signal_connect(G_OBJECT(drawing_area
), "leave_notify_event", G_CALLBACK(drawing_area_leave_event
), NULL
);
1885 g_signal_connect(G_OBJECT(drawing_area
), "expose_event", G_CALLBACK(drawing_area_expose_event
), NULL
);
1886 gtk_container_add(GTK_CONTAINER(align
), drawing_area
);
1888 delete rendered_cave
;
1891 int cs
=editor_cell_renderer
->get_cell_size();
1892 screen
->set_size(edited_cave
->w
*cs
, edited_cave
->h
*cs
);
1895 /* if no cave given, show selector. */
1896 /* forget undo caves */
1899 gfx_buffer
.remove();
1900 object_highlight_map
.remove();
1901 delete rendered_cave
;
1904 gtk_list_store_clear(object_list
);
1905 gtk_widget_hide(scroll_window_objects
);
1908 gtk_widget_destroy(drawing_area
->parent
->parent
);
1909 /* parent is the align, parent of align is the viewport automatically added. */
1911 if (!iconview_cavelist
) {
1912 GtkListStore
*cave_list
;
1914 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll_window
), GTK_SHADOW_IN
);
1916 /* create list store for caveset */
1917 cave_list
=gtk_list_store_new(NUM_CAVESET_COLUMNS
, G_TYPE_POINTER
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
);
1918 for (unsigned n
=0; n
<caveset
->caves
.size(); n
++)
1919 icon_view_add_cave(cave_list
, &caveset
->cave(n
));
1920 /* we only connect this signal after adding all caves to the icon view, so it is only activated by the user! */
1921 g_signal_connect(G_OBJECT(cave_list
), "row-inserted", G_CALLBACK(icon_view_row_inserted
), NULL
);
1922 /* forget caveset; now we store caves in the GtkListStore */
1923 caveset
->caves
.clear_i_own_them();
1925 iconview_cavelist
=gtk_icon_view_new_with_model (GTK_TREE_MODEL (cave_list
));
1926 g_object_unref(cave_list
); /* now the icon view holds the reference */
1927 icon_view_update_pixbufs(); /* create icons */
1928 g_signal_connect(G_OBJECT(iconview_cavelist
), "destroy", G_CALLBACK(icon_view_destroyed
), &iconview_cavelist
);
1929 g_signal_connect(G_OBJECT(iconview_cavelist
), "destroy", G_CALLBACK(gtk_widget_destroyed
), &iconview_cavelist
);
1930 g_signal_connect(G_OBJECT(iconview_cavelist
), "popup-menu", G_CALLBACK(icon_view_popup_menu
), NULL
);
1931 g_signal_connect(G_OBJECT(iconview_cavelist
), "button-press-event", G_CALLBACK(icon_view_button_press_event
), NULL
);
1933 gtk_icon_view_set_text_column(GTK_ICON_VIEW(iconview_cavelist
), NAME_COLUMN
);
1934 gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW (iconview_cavelist
), PIXBUF_COLUMN
);
1935 gtk_icon_view_set_item_width(GTK_ICON_VIEW(iconview_cavelist
), 128+24); /* 128 is the size of the icons */
1936 gtk_icon_view_set_reorderable(GTK_ICON_VIEW(iconview_cavelist
), TRUE
);
1937 gtk_icon_view_set_selection_mode(GTK_ICON_VIEW (iconview_cavelist
), GTK_SELECTION_MULTIPLE
);
1938 /* item (cave) activated. the enter button activates the menu item; this one is used for doubleclick */
1939 g_signal_connect(iconview_cavelist
, "item-activated", G_CALLBACK(icon_view_edit_cave_cb
), NULL
);
1940 g_signal_connect(iconview_cavelist
, "selection-changed", G_CALLBACK(icon_view_selection_changed_cb
), NULL
);
1941 gtk_container_add(GTK_CONTAINER (scroll_window
), iconview_cavelist
);
1944 set_status_label_for_caveset();
1946 /* show all items inside scrolled window. some may have been newly created. */
1947 gtk_widget_show_all(scroll_window
);
1949 /* hide toolbars if not editing a cave */
1951 gtk_widget_show (toolbars
);
1953 gtk_widget_hide (toolbars
);
1955 editor_window_set_title();
1958 /****************************************************/
1960 cave_random_setup_cb(GtkWidget
*widget
, gpointer data
) {
1961 g_return_if_fail(edited_cave
->map
.empty());
1965 GtkWidget
*dialog
, *notebook
;
1966 edit_properties_create_window(_("Cave Random Fill"), false, dialog
, notebook
);
1968 std::list
<EditorAutoUpdate
*> eau_s
;
1969 edit_properties_add_widgets(notebook
, eau_s
, CaveStored::random_dialog
, edited_cave
, edited_cave
, render_cave
);
1972 gd_dialog_add_hint(GTK_DIALOG(dialog
), _("Hint: The random fill works by generating a random number between 0 and "
1973 "255, then choosing an element from the list above. If the number generated is bigger than probability 1, "
1974 "initial fill is chosen. If smaller than probability 1, but bigger than probability 2, the first element is chosen "
1975 "and so on. GDash will make sure that the probability values come in descending order."));
1977 gtk_widget_show_all(dialog
);
1978 int result
=gtk_dialog_run(GTK_DIALOG(dialog
));
1979 gtk_widget_destroy(dialog
);
1981 for (std::list
<EditorAutoUpdate
*>::const_iterator it
=eau_s
.begin(); it
!=eau_s
.end(); ++it
)
1984 /* if not accepted by user, revert to original */
1985 if (result
!=GTK_RESPONSE_ACCEPT
)
1986 undo_do_one_step_but_no_redo();
1990 /*******************************************
1994 *******************************************/
1996 save_cave_png (GdkPixbuf
*pixbuf
) {
1997 /* if no filename given, */
1999 GtkFileFilter
*filter
;
2001 char *filename
=NULL
;
2003 /* check if in cave editor */
2004 g_return_if_fail (edited_cave
!=NULL
);
2006 dialog
=gtk_file_chooser_dialog_new(_("Save Cave as PNG Image"), GTK_WINDOW(gd_editor_window
), GTK_FILE_CHOOSER_ACTION_SAVE
,
2007 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2008 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2010 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
2012 filter
=gtk_file_filter_new();
2013 gtk_file_filter_set_name (filter
, _("PNG files"));
2014 gtk_file_filter_add_pattern (filter
, "*.png");
2015 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog
), filter
);
2016 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
), CPrintf("%s.png") % edited_cave
->name
);
2017 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
), TRUE
);
2019 if (gtk_dialog_run(GTK_DIALOG(dialog
))==GTK_RESPONSE_ACCEPT
) {
2020 filename
=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog
));
2021 if (!g_str_has_suffix(filename
, ".png")) {
2022 char *suffixed
=g_strdup_printf("%s.png", filename
);
2028 gdk_pixbuf_save(pixbuf
, filename
, "png", &error
, "compression", "9", NULL
);
2031 gd_errormessage(error
->message
, NULL
);
2032 g_error_free(error
);
2034 gtk_widget_destroy(dialog
);
2039 this creates a pixbuf of the cave, and scales it down to fit the screen if needed.
2040 it is then presented to the user, with the option to save it in png */
2041 static void cave_overview(gboolean simple_view
) {
2042 /* view the RENDERED one, and the entire cave */
2043 GdkPixbuf
*pixbuf
=gd_drawcave_to_pixbuf(rendered_cave
, *editor_cell_renderer
, 0, 0, simple_view
, true), *scaled
;
2044 GtkWidget
*dialog
, *button
;
2049 /* be careful not to use entire screen, there are window title and close button */
2050 sx
=gdk_screen_get_width(gdk_screen_get_default())-64;
2051 sy
=gdk_screen_get_height(gdk_screen_get_default())-128;
2052 if (gdk_pixbuf_get_width(pixbuf
)>sx
)
2053 fx
=(double) sx
/gdk_pixbuf_get_width(pixbuf
);
2056 if (gdk_pixbuf_get_height(pixbuf
)>sy
)
2057 fy
=(double) sy
/gdk_pixbuf_get_height(pixbuf
);
2060 /* whichever is smaller */
2065 /* if we have to make it smaller */
2066 if (fx
!=1.0 || fy
!=1.0)
2067 scaled
=gdk_pixbuf_scale_simple(pixbuf
, gdk_pixbuf_get_width(pixbuf
)*fx
, gdk_pixbuf_get_height(pixbuf
)*fy
, GDK_INTERP_BILINEAR
);
2070 g_object_ref(scaled
);
2073 /* simple dialog with this image only */
2074 dialog
=gtk_dialog_new_with_buttons(_("Cave Overview"), GTK_WINDOW(gd_editor_window
), GtkDialogFlags(GTK_DIALOG_NO_SEPARATOR
| GTK_DIALOG_DESTROY_WITH_PARENT
), NULL
);
2075 button
=gtk_button_new_with_mnemonic(_("Save as _PNG"));
2076 gtk_button_set_image(GTK_BUTTON(button
), gtk_image_new_from_stock(GTK_STOCK_CONVERT
, GTK_ICON_SIZE_BUTTON
));
2077 gtk_dialog_add_action_widget(GTK_DIALOG(dialog
), button
, 1);
2078 gtk_dialog_add_button(GTK_DIALOG(dialog
), GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
);
2079 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_CLOSE
);
2081 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), gtk_image_new_from_pixbuf(scaled
));
2083 gtk_widget_show_all(dialog
);
2084 response
=gtk_dialog_run(GTK_DIALOG(dialog
));
2085 gtk_widget_destroy(dialog
);
2087 save_cave_png(pixbuf
);
2088 g_object_unref(pixbuf
);
2089 g_object_unref(scaled
);
2094 cave_overview_cb(GtkWidget
*widget
, gpointer data
) {
2095 cave_overview(FALSE
);
2099 cave_overview_simple_cb(GtkWidget
*widget
, gpointer data
) {
2100 cave_overview(TRUE
);
2106 if last line or last row is just steel wall (or (invisible) outbox).
2107 used after loading a game for playing.
2108 after this, ew and eh will contain the effective width and height.
2111 rendered_cave_auto_shrink(CaveRendered
*cave
) {
2118 /* set to maximum size, then try to shrink */
2119 cave
->x1
=0; cave
->y1
=0;
2120 cave
->x2
=cave
->w
-1; cave
->y2
=cave
->h
-1;
2122 /* search for empty, steel-wall-only last rows. */
2123 /* clear all lines, which are only steel wall.
2124 * and clear only one line, which is steel wall, but also has a player or an outbox. */
2127 for (int y
=cave
->y2
-1; y
<=cave
->y2
; y
++)
2128 for (int x
=cave
->x1
; x
<=cave
->x2
; x
++)
2129 switch (cave
->map(x
, y
)) {
2130 case O_STEEL
: /* if steels only, this is to be deleted. */
2133 case O_PRE_INVIS_OUTBOX
:
2135 if (empty
==STEEL_OR_OTHER
)
2137 if (empty
==STEEL_ONLY
) /* if this, delete only this one, and exit. */
2138 empty
=STEEL_OR_OTHER
;
2140 default: /* anything else, that should be left in the cave. */
2144 if (empty
!=NO_SHRINK
) /* shrink if full steel or steel and player/outbox. */
2145 cave
->y2
--; /* one row shorter */
2147 while (empty
==STEEL_ONLY
); /* if found just steels, repeat. */
2149 /* search for empty, steel-wall-only first rows. */
2152 for (int y
=cave
->y1
; y
<=cave
->y1
+1; y
++)
2153 for (int x
=cave
->x1
; x
<=cave
->x2
; x
++)
2154 switch (cave
->map(x
, y
)) {
2158 case O_PRE_INVIS_OUTBOX
:
2160 /* shrink only lines, which have only ONE player or outbox. this is for bd4 intermission 2, for example. */
2161 if (empty
==STEEL_OR_OTHER
)
2163 if (empty
==STEEL_ONLY
)
2164 empty
=STEEL_OR_OTHER
;
2170 if (empty
!=NO_SHRINK
)
2173 while (empty
==STEEL_ONLY
); /* if found one, repeat. */
2175 /* empty last columns. */
2178 for (int y
=cave
->y1
; y
<=cave
->y2
; y
++)
2179 for (int x
=cave
->x2
-1; x
<=cave
->x2
; x
++)
2180 switch (cave
->map(x
, y
)) {
2184 case O_PRE_INVIS_OUTBOX
:
2186 if (empty
==STEEL_OR_OTHER
)
2188 if (empty
==STEEL_ONLY
)
2189 empty
=STEEL_OR_OTHER
;
2195 if (empty
!=NO_SHRINK
)
2196 cave
->x2
--; /* just remember that one column shorter. g_free will know the size of memchunk, no need to realloc! */
2198 while (empty
==STEEL_ONLY
); /* if found one, repeat. */
2200 /* empty first columns. */
2203 for (int y
=cave
->y1
; y
<=cave
->y2
; y
++)
2204 for (int x
=cave
->x1
; x
<=cave
->x1
+1; x
++)
2205 switch (cave
->map(x
, y
)) {
2209 case O_PRE_INVIS_OUTBOX
:
2211 if (empty
==STEEL_OR_OTHER
)
2213 if (empty
==STEEL_ONLY
)
2214 empty
=STEEL_OR_OTHER
;
2220 if (empty
!=NO_SHRINK
)
2223 while (empty
==STEEL_ONLY
); /* if found one, repeat. */
2227 /* automatically shrink cave
2230 auto_shrink_cave_cb(GtkWidget
*widget
, gpointer data
) {
2232 /* shrink the rendered cave, as it has all object and the like converted to a map. */
2233 rendered_cave_auto_shrink(rendered_cave
);
2234 /* then copy the results to the original */
2235 edited_cave
->x1
=rendered_cave
->x1
;
2236 edited_cave
->y1
=rendered_cave
->y1
;
2237 edited_cave
->x2
=rendered_cave
->x2
;
2238 edited_cave
->y2
=rendered_cave
->y2
;
2240 /* re-render cave; after that, selecting visible region tool allows the user to see the result, maybe modify */
2241 render_cave(); /* not really needed? does not hurt, anyway. */
2242 select_tool(TOOL_VISIBLE_REGION
);
2248 * SET CAVE COLORS WITH INSTANT UPDATE TOOL.
2251 static gboolean cave_colors_colorchange_update_disabled
;
2253 /* helper: update pixmaps and the like */
2255 cave_colors_update_element_pixbufs() {
2256 if (cave_colors_colorchange_update_disabled
)
2258 /* select new colors - render cave does not do this */
2259 editor_cell_renderer
->select_pixbuf_colors(edited_cave
->color0
, edited_cave
->color1
, edited_cave
->color2
, edited_cave
->color3
, edited_cave
->color4
, edited_cave
->color5
);
2260 /* update element buttons in editor (under toolbar) */
2261 gd_element_button_update_pixbuf(element_button
);
2262 gd_element_button_update_pixbuf(fillelement_button
);
2263 /* clear gfx buffer, so every element gets redrawn */
2264 gfx_buffer
.fill(-1);
2265 /* for object list update with new pixbufs */
2270 /* when the random colors button is pressed, first we change the colors of the cave. */
2271 /* then we update the combo boxes one by one (they are in a glist *), but before that, */
2272 /* we disable their updating behaviour. otherwise they would re-render pixmaps one by one, */
2273 /* and they would also want to change the cave itself - the cave which already contains the */
2274 /* changed colors! */
2276 cave_colors_random_combo_cb(GtkWidget
*widget
, gpointer data
) {
2277 std::list
<EditorAutoUpdate
*> *eau_s
=static_cast<std::list
<EditorAutoUpdate
*> *>(data
);
2280 new_index
=gtk_combo_box_get_active(GTK_COMBO_BOX(widget
));
2281 if (new_index
==0) /* 0 is the "set random..." text, so we do nothing */
2285 gd_cave_set_random_colors(*edited_cave
, GdColor::Type(new_index
-1)); /* -1: 0 is "set random...", 1 is rgb... */
2287 /* and update combo boxes from cave */
2288 cave_colors_colorchange_update_disabled
=TRUE
; /* this is needed, otherwise all combos would want to update the pixbufs, one by one */
2289 for (std::list
<EditorAutoUpdate
*>::const_iterator it
=eau_s
->begin(); it
!=eau_s
->end(); ++it
)
2291 cave_colors_colorchange_update_disabled
=FALSE
;
2292 cave_colors_update_element_pixbufs();
2294 /* set back to "select random..." text */
2295 gtk_combo_box_set_active(GTK_COMBO_BOX(widget
), 0);
2298 /* set cave colors with instant update */
2299 static void cave_colors_cb(GtkWidget
*widget
, gpointer data
) {
2302 /* when editing colors, turn off the colored objects viewing for a while. */
2303 bool colored_objects_backup
=gd_colored_objects
;
2304 gd_colored_objects
=false;
2306 GtkWidget
*dialog
, *notebook
;
2307 edit_properties_create_window(_("Cave Colors"), false, dialog
, notebook
);
2309 GtkWidget
*random_combo
=gtk_combo_box_new_text();
2310 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->action_area
), random_combo
, FALSE
, FALSE
, 0);
2311 gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(dialog
)->action_area
), random_combo
, 0);
2313 std::list
<EditorAutoUpdate
*> eau_s
;
2314 edit_properties_add_widgets(notebook
, eau_s
, CaveStored::color_dialog
, edited_cave
, edited_cave
, cave_colors_update_element_pixbufs
);
2316 /* a combo box which has a callback that sets random colors */
2317 gtk_combo_box_append_text(GTK_COMBO_BOX(random_combo
), _("Set random...")); /* will be active=0 */
2318 for (int i
=0; GdColor::get_palette_types_names()[i
]!=NULL
; i
++)
2319 gtk_combo_box_append_text(GTK_COMBO_BOX(random_combo
), _(GdColor::get_palette_types_names()[i
]));
2320 gtk_combo_box_set_active(GTK_COMBO_BOX(random_combo
), 0);
2321 g_signal_connect(random_combo
, "changed", G_CALLBACK(cave_colors_random_combo_cb
), &eau_s
);
2324 gd_dialog_add_hint(GTK_DIALOG(dialog
), _("Hint: As the palette can be changed for C64 and Atari colors, "
2325 "it is not recommended to use different types together (for example, RGB color for background, Atari color for Slime.)"));
2327 gtk_widget_show_all(dialog
);
2328 cave_colors_colorchange_update_disabled
=FALSE
;
2329 int result
=gtk_dialog_run(GTK_DIALOG(dialog
));
2330 gtk_widget_destroy(dialog
);
2332 for (std::list
<EditorAutoUpdate
*>::const_iterator it
=eau_s
.begin(); it
!=eau_s
.end(); ++it
)
2334 /* if the new colors were not accepted by the user (escape pressed), we undo the changes. */
2335 if (result
!=GTK_RESPONSE_ACCEPT
)
2336 undo_do_one_step_but_no_redo();
2338 /* restore colored objects setting. */
2339 gd_colored_objects
=colored_objects_backup
;
2343 /***************************************************
2345 * CAVE EDITING CALLBACKS
2349 /* delete selected cave drawing element or cave.
2352 delete_selected_cb(GtkWidget
*widget
, gpointer data
) {
2353 /* deleting caves or cave object. */
2354 if (edited_cave
==NULL
) {
2355 /* WE ARE DELETING ONE OR MORE CAVES HERE */
2357 /* first we ask the user if he is sure, as no undo is implemented yet */
2358 gboolean response
= gd_question_yesno(_("Do you really want to delete cave(s)?"), _("This operation cannot be undone."));
2362 GList
*list
= gtk_icon_view_get_selected_items (GTK_ICON_VIEW (iconview_cavelist
));
2363 g_return_if_fail (list
!=NULL
); /* list should be not empty. otherwise why was the button not insensitized? */
2365 /* if anything was selected */
2366 GtkTreeModel
*model
= gtk_icon_view_get_model(GTK_ICON_VIEW(iconview_cavelist
));
2367 /* for all caves selected, convert to tree row references - we must delete them for the icon view, so this is necessary */
2368 GList
*references
=NULL
;
2369 for (GList
*listiter
= list
; listiter
!=NULL
; listiter
=listiter
->next
)
2370 references
=g_list_append(references
, gtk_tree_row_reference_new(model
, (GtkTreePath
*)listiter
->data
));
2371 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
2374 /* now check the list of references and delete each cave */
2375 for (GList
*listiter
= references
; listiter
!=NULL
; listiter
=listiter
->next
) {
2376 GtkTreeRowReference
*reference
=(GtkTreeRowReference
*)listiter
->data
;
2377 GtkTreePath
*path
= gtk_tree_row_reference_get_path(reference
);
2379 gtk_tree_model_get_iter(model
, &iter
, path
);
2381 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
2382 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
2383 delete cave
; /* and also free memory associated. */
2384 g_hash_table_remove(cave_pixbufs
, cave
);
2386 g_list_foreach(references
, (GFunc
) gtk_tree_row_reference_free
, NULL
);
2387 g_list_free(references
);
2389 /* this modified the caveset */
2390 caveset
->edited
=TRUE
;
2392 /* WE ARE DELETING A CAVE OBJECT HERE */
2393 g_return_if_fail(object_list_is_any_selected());
2397 /* delete all objects */
2398 edited_cave
->objects
.remove_if(object_list_is_selected
);
2399 object_list_clear_selection();
2404 /* put selected drawing elements to bottom.
2406 static void send_to_back_selected_cb(GtkWidget
*widget
, gpointer data
) {
2407 g_return_if_fail(object_list_is_any_selected());
2410 std::stable_partition(edited_cave
->objects
.begin(), edited_cave
->objects
.end(), object_list_is_selected
);
2414 /* bring selected drawing element to top. */
2415 static void bring_to_front_selected_cb(GtkWidget
*widget
, gpointer data
) {
2416 g_return_if_fail(object_list_is_any_selected());
2419 std::stable_partition(edited_cave
->objects
.begin(), edited_cave
->objects
.end(), object_list_is_not_selected
);
2423 /* enable currently selected objects on the currently viewed level only. */
2425 show_object_this_level_only_cb(GtkWidget
*widget
, gpointer data
) {
2426 g_return_if_fail (object_list_is_any_selected());
2430 for (std::set
<CaveObject
*>::iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
) {
2431 CaveObject
*obj
=*it
;
2433 obj
->disable_on_all();
2434 obj
->seen_on
[edit_level
]=true;
2439 /* enable currently selected objects on all levels */
2441 show_object_all_levels_cb(GtkWidget
*widget
, gpointer data
) {
2442 g_return_if_fail (object_list_is_any_selected());
2446 for (std::set
<CaveObject
*>::iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
) {
2447 CaveObject
*obj
=*it
;
2449 obj
->enable_on_all();
2454 /* enable currently selected objects on the currently viewed level only. */
2456 show_object_on_this_level_cb(GtkWidget
*widget
, gpointer data
) {
2457 g_return_if_fail (object_list_is_any_selected());
2461 for (std::set
<CaveObject
*>::iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
) {
2462 CaveObject
*obj
=*it
;
2464 obj
->seen_on
[edit_level
]=true;
2469 /* enable currently selected objects on the currently viewed level only. */
2471 hide_object_on_this_level_cb(GtkWidget
*widget
, gpointer data
) {
2472 g_return_if_fail (object_list_is_any_selected());
2477 for (std::set
<CaveObject
*>::iterator it
=selected_objects
.begin(); it
!=selected_objects
.end(); ++it
) {
2478 CaveObject
*obj
=*it
;
2480 obj
->seen_on
[edit_level
]=false;
2481 /* an object should be visible on at least one level. */
2482 /* if it disappeared, switch it back, and remember that we will show an error message. */
2483 if (obj
->is_invisible()) {
2484 obj
->seen_on
[edit_level
]=true;
2491 gd_warningmessage(_("At least one object would have been totally hidden (not visible on any of the levels)."), _("Enabled those objects on the current level."));
2494 /* copy selected object or caves to clipboard.
2497 copy_selected_cb(GtkWidget
*widget
, gpointer data
) {
2498 if (edited_cave
==NULL
) {
2499 /* WE ARE NOW COPYING CAVES FROM A CAVESET */
2500 GList
*list
, *listiter
;
2501 GtkTreeModel
*model
;
2503 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW (iconview_cavelist
));
2504 g_return_if_fail (list
!=NULL
); /* list should be not empty. otherwise why was the button not insensitized? */
2506 /* forget old clipboard */
2507 cave_clipboard
.clear();
2509 /* now for all caves selected */
2510 /* we do not need references here (as in cut), as we do not modify the treemodel */
2511 model
=gtk_icon_view_get_model(GTK_ICON_VIEW(iconview_cavelist
));
2512 for (listiter
=list
; listiter
!=NULL
; listiter
=listiter
->next
) {
2513 CaveStored
*cave
=NULL
;
2516 gtk_tree_model_get_iter (model
, &iter
, (GtkTreePath
*) listiter
->data
);
2517 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
2518 /* add to clipboard: prepend must be used for correct order */
2519 /* here, a COPY is added to the clipboard */
2520 cave_clipboard
.push_front(*cave
);
2522 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
2525 /* enable pasting */
2526 gtk_action_group_set_sensitive (actions_clipboard_paste
, TRUE
);
2528 /* delete contents of clipboard */
2529 object_clipboard
=object_list_copy_of_selected();
2531 /* enable pasting */
2532 gtk_action_group_set_sensitive (actions_clipboard_paste
, TRUE
);
2536 /* paste object or cave from clipboard
2539 paste_clipboard_cb(GtkWidget
*widget
, gpointer data
) {
2540 if (edited_cave
==NULL
) {
2541 /* WE ARE IN THE CAVESET ICON VIEW */
2542 GtkListStore
*store
=GTK_LIST_STORE(gtk_icon_view_get_model(GTK_ICON_VIEW(iconview_cavelist
)));
2544 for (std::list
<CaveStored
>::const_iterator it
=cave_clipboard
.begin(); it
!=cave_clipboard
.end(); ++it
)
2545 icon_view_add_cave(store
, new CaveStored(*it
));
2546 icon_view_update_pixbufs();
2548 /* this modified the caveset */
2549 caveset
->edited
=TRUE
;
2551 /* WE ARE IN THE CAVE EDITOR */
2552 std::list
<CaveObject
*> newly_added_objects
;
2554 g_return_if_fail (!object_clipboard
.empty());
2556 /* we have a list of newly added (pasted) objects, so after pasting we can
2557 select them. this is necessary, as only after pasting is render_cave() called,
2558 which adds the new objects to the gtkliststore. otherwise that one would not
2559 contain cave objects. the clipboard also cannot be used, as pointers are different */
2560 for (CaveObjectStore::const_iterator it
=object_clipboard
.begin(); it
!=object_clipboard
.end(); ++it
) {
2561 CaveObject
*cloned
=(*it
)->clone();
2562 edited_cave
->objects
.push_back_adopt(cloned
);
2563 newly_added_objects
.push_back(cloned
);
2567 object_list_clear_selection();
2568 for_each(newly_added_objects
.begin(), newly_added_objects
.end(), object_list_add_to_selection
);
2573 /* cut an object, or cave(s) from the caveset. */
2575 cut_selected_cb(GtkWidget
*widget
, gpointer data
) {
2576 if (edited_cave
==NULL
) {
2577 /* WE ARE NOW CUTTING CAVES FROM A CAVESET */
2578 GList
*list
, *listiter
;
2579 GtkTreeModel
*model
;
2580 GList
*references
=NULL
;
2582 list
=gtk_icon_view_get_selected_items (GTK_ICON_VIEW (iconview_cavelist
));
2583 g_return_if_fail (list
!=NULL
); /* list should be not empty. otherwise why was the button not insensitized? */
2585 /* forget old clipboard */
2586 cave_clipboard
.clear();
2588 /* if anything was selected */
2589 model
=gtk_icon_view_get_model (GTK_ICON_VIEW (iconview_cavelist
));
2590 /* for all caves selected, convert to tree row references - we must delete them for the icon view, so this is necessary */
2591 for (listiter
=list
; listiter
!=NULL
; listiter
=listiter
->next
)
2592 references
=g_list_append(references
, gtk_tree_row_reference_new(model
, (GtkTreePath
*)listiter
->data
));
2593 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
2596 for (listiter
=references
; listiter
!=NULL
; listiter
=listiter
->next
) {
2597 GtkTreeRowReference
*reference
=(GtkTreeRowReference
*) listiter
->data
;
2599 CaveStored
*cave
=NULL
;
2602 path
=gtk_tree_row_reference_get_path(reference
);
2603 gtk_tree_model_get_iter (model
, &iter
, path
);
2604 gtk_tree_model_get (model
, &iter
, CAVE_COLUMN
, &cave
, -1);
2605 /* prepend must be used for correct order */
2606 /* here, the cave is not copied, but the pointer is moved to the clipboard */
2607 cave_clipboard
.push_front(*cave
);
2608 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
2609 /* remove its pixbuf */
2610 g_hash_table_remove(cave_pixbufs
, cave
);
2612 g_list_foreach(references
, (GFunc
) gtk_tree_row_reference_free
, NULL
);
2613 g_list_free(references
);
2615 /* enable pasting */
2616 gtk_action_group_set_sensitive (actions_clipboard_paste
, TRUE
);
2618 /* this modified the caveset */
2619 caveset
->edited
=TRUE
;
2621 /* EDITED OBJECT IS NOT NULL, SO WE ARE CUTTING OBJECTS */
2624 /* delete contents of clipboard */
2625 object_clipboard
.clear();
2627 object_clipboard
=object_list_copy_of_selected();
2628 edited_cave
->objects
.remove_if(object_list_is_selected
);
2630 /* enable pasting */
2631 gtk_action_group_set_sensitive (actions_clipboard_paste
, TRUE
);
2633 object_list_clear_selection();
2639 select_all_cb(GtkWidget
*widget
, gpointer data
) {
2640 if (edited_cave
==NULL
) /* in game editor */
2641 gtk_icon_view_select_all(GTK_ICON_VIEW(iconview_cavelist
)); /* SELECT ALL CAVES */
2642 else /* in cave editor */
2643 gtk_tree_selection_select_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
))); /* SELECT ALL OBJECTS */
2646 /* delete map from cave */
2648 remove_map_cb(GtkWidget
*widget
, gpointer data
) {
2649 g_return_if_fail (edited_cave
!=NULL
);
2650 g_return_if_fail (!edited_cave
->map
.empty());
2652 gboolean response
= gd_question_yesno(_("Do you really want to remove cave map?"), _("This operation destroys all cave objects."));
2655 undo_save(); /* changing cave; save for undo */
2657 edited_cave
->map
.remove();
2658 /* map deleted; redraw cave */
2664 /* flatten cave -> pack everything in a map */
2666 flatten_cave_cb(GtkWidget
*widget
, gpointer data
) {
2669 g_return_if_fail (edited_cave
!=NULL
);
2671 if (edited_cave
->objects
.empty()) {
2672 gd_infomessage(_("This cave has no objects."), NULL
);
2676 response
=gd_question_yesno(_("Do you really want to flatten cave?"), _("This operation merges all cave objects currently seen in a single map. Further objects may later be added, but the ones already seen will behave like the random fill elements; they will not be editable."));
2679 undo_save(); /* changing; save for undo */
2681 CaveRendered
rendered(*edited_cave
, edit_level
, 0); /* render cave at specified level to obtain map. seed=0 */
2682 edited_cave
->map
=rendered
.map
; /* copy new map to cave */
2683 edited_cave
->objects
.clear(); /* forget objects */
2684 render_cave(); /* redraw */
2688 /* shift cave map left, one step. */
2690 shift_left_cb(GtkWidget
*widget
, gpointer data
) {
2691 CaveMap
<GdElementEnum
> mapcopy(edited_cave
->map
);
2692 mapcopy
.set_wrap_type(CaveMapBase::Perfect
);
2693 for (int y
=0; y
<edited_cave
->h
; y
++)
2694 for (int x
=0; x
<edited_cave
->w
; x
++)
2695 edited_cave
->map(x
, y
)=mapcopy(x
+1, y
);
2699 /* shift cave map right, one step. */
2701 shift_right_cb(GtkWidget
*widget
, gpointer data
) {
2702 CaveMap
<GdElementEnum
> mapcopy(edited_cave
->map
);
2703 mapcopy
.set_wrap_type(CaveMapBase::Perfect
);
2704 for (int y
=0; y
<edited_cave
->h
; y
++)
2705 for (int x
=0; x
<edited_cave
->w
; x
++)
2706 edited_cave
->map(x
, y
)=mapcopy(x
-1, y
);
2710 /* shift cave map up, one step. */
2712 shift_up_cb(GtkWidget
*widget
, gpointer data
) {
2713 CaveMap
<GdElementEnum
> mapcopy(edited_cave
->map
);
2714 mapcopy
.set_wrap_type(CaveMapBase::Perfect
);
2715 for (int y
=0; y
<edited_cave
->h
; y
++)
2716 for (int x
=0; x
<edited_cave
->w
; x
++)
2717 edited_cave
->map(x
, y
)=mapcopy(x
, y
+1);
2721 /* shift cave map down, one step. */
2723 shift_down_cb(GtkWidget
*widget
, gpointer data
) {
2724 CaveMap
<GdElementEnum
> mapcopy(edited_cave
->map
);
2725 mapcopy
.set_wrap_type(CaveMapBase::Perfect
);
2726 for (int y
=0; y
<edited_cave
->h
; y
++)
2727 for (int x
=0; x
<edited_cave
->w
; x
++)
2728 edited_cave
->map(x
, y
)=mapcopy(x
, y
-1);
2734 set_engine_default_cb(GtkWidget
*widget
, gpointer data
) {
2735 GdEngineEnum e
=GdEngineEnum(GPOINTER_TO_INT(data
));
2737 g_assert(e
>=0 && e
<GD_ENGINE_MAX
);
2738 g_assert(edited_cave
!=NULL
);
2741 C64Import::cave_set_engine_defaults(*edited_cave
, e
);
2743 // props=gd_struct_explain_defaults_in_string(CaveStored::descriptor, gd_get_engine_default_array(e));
2744 // gd_infomessage(_("The following properties are set:"), props);
2750 save_html_cb(GtkWidget
*widget
, gpointer data
) {
2751 char *htmlname
=NULL
, *suggested_name
;
2752 /* if no filename given, */
2754 GtkFileFilter
*filter
;
2758 /* destroy icon view so it does not interfere with saving (when icon view is active, caveset=NULL) */
2759 if (iconview_cavelist
)
2760 gtk_widget_destroy(iconview_cavelist
);
2762 dialog
=gtk_file_chooser_dialog_new(_("Save Cave Set in HTML"), GTK_WINDOW(gd_editor_window
),
2763 GTK_FILE_CHOOSER_ACTION_SAVE
,
2764 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2765 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2767 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
2769 filter
=gtk_file_filter_new();
2770 gtk_file_filter_set_name (filter
, _("HTML files"));
2771 gtk_file_filter_add_pattern (filter
, "*.html");
2772 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog
), filter
);
2774 suggested_name
=g_strdup_printf("%s.html", caveset
->name
.c_str());
2775 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
), suggested_name
);
2776 g_free(suggested_name
);
2778 if (gtk_dialog_run(GTK_DIALOG(dialog
))==GTK_RESPONSE_ACCEPT
)
2779 htmlname
=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog
));
2780 gtk_widget_destroy(dialog
);
2782 /* saving if got filename */
2785 gd_save_html(htmlname
, gd_editor_window
, *caveset
);
2786 gd_show_errors(l
, _("Errors - Saving Gallery to File"));
2789 select_cave_for_edit(edited
); /* go back to edited cave or recreate icon view */
2793 /* export cave to a crli editor format */
2794 static void export_cavefile_cb(GtkWidget
*widget
, gpointer data
) {
2796 /* if no filename given, */
2799 g_return_if_fail(edited_cave
!=NULL
);
2801 dialog
=gtk_file_chooser_dialog_new(_("Export Cave as CrLi Cave File"), GTK_WINDOW(gd_editor_window
),
2802 GTK_FILE_CHOOSER_ACTION_SAVE
,
2803 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2804 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2806 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
2808 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
), edited_cave
->name
.c_str());
2809 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
), TRUE
);
2811 if (gtk_dialog_run(GTK_DIALOG(dialog
))==GTK_RESPONSE_ACCEPT
)
2812 outname
=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog
));
2813 gtk_widget_destroy(dialog
);
2815 /* if accepted, save. */
2818 gd_export_cave_to_crli_cavefile(edited_cave
, edit_level
, outname
);
2819 gd_show_errors(l
, _("Errors - Exporting Cave to CrLi Format"));
2825 /* export complete caveset to a crli cave pack */
2826 static void export_cavepack_cb(GtkWidget
*widget
, gpointer data
) {
2828 /* if no filename given, */
2833 /* destroy icon view so it does not interfere with saving (when icon view is active, caveset=NULL) */
2834 if (iconview_cavelist
)
2835 gtk_widget_destroy(iconview_cavelist
);
2837 dialog
=gtk_file_chooser_dialog_new(_("Export Cave as CrLi Cave Pack"), GTK_WINDOW(gd_editor_window
),
2838 GTK_FILE_CHOOSER_ACTION_SAVE
,
2839 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
2840 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
2842 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
2844 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
), caveset
->name
.c_str());
2845 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
), TRUE
);
2847 if (gtk_dialog_run(GTK_DIALOG(dialog
))==GTK_RESPONSE_ACCEPT
)
2848 outname
=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog
));
2849 gtk_widget_destroy(dialog
);
2851 /* saving if got filename */
2854 std::vector
<CaveStored
*> caves_to_export
;
2855 for (unsigned i
=0; i
<caveset
->caves
.size(); ++i
)
2856 caves_to_export
.push_back(&caveset
->cave(i
));
2857 gd_export_caves_to_crli_cavepack(caves_to_export
, edit_level
, outname
);
2858 gd_show_errors(l
, _("Errors - Exporting Caves to File"));
2862 select_cave_for_edit(edited
); /* go back to edited cave or recreate icon view */
2866 /* test selected level. */
2868 play_level_cb(GtkWidget
*widget
, gpointer data
) {
2869 g_return_if_fail(edited_cave
!=NULL
);
2870 gtk_widget_set_sensitive(gd_editor_window
, FALSE
);
2871 GameControl
*game
= GameControl::new_test(edited_cave
, edit_level
);
2872 gd_main_window_sdl_run_a_game(game
);
2873 gtk_widget_set_sensitive(gd_editor_window
, TRUE
);
2878 object_properties_cb(GtkWidget
*widget
, gpointer data
) {
2879 object_properties(NULL
);
2883 /* edit caveset properties */
2885 set_caveset_properties_cb(GtkWidget
*widget
, gpointer data
) {
2886 caveset_properties(true);
2891 cave_properties_cb(const GtkWidget
*widget
, const gpointer data
) {
2892 cave_properties (edited_cave
, TRUE
);
2896 /************************************************
2902 /* go to cave selector */
2904 cave_selector_cb(GtkWidget
*widget
, gpointer data
) {
2905 select_cave_for_edit(NULL
);
2908 /* view next cave */
2910 previous_cave_cb(GtkWidget
*widget
, gpointer data
) {
2912 g_return_if_fail(edited_cave
!=NULL
);
2913 g_return_if_fail(caveset
->has_caves());
2915 i
=caveset
->cave_index(edited_cave
);
2916 g_return_if_fail(i
!=-1);
2917 i
=(i
-1+caveset
->caves
.size())%caveset
->caves
.size();
2918 select_cave_for_edit(&caveset
->cave(i
));
2921 /* go to cave selector */
2923 next_cave_cb(GtkWidget
*widget
, gpointer data
) {
2925 g_return_if_fail(edited_cave
!=NULL
);
2926 g_return_if_fail(caveset
->has_caves());
2928 i
=caveset
->cave_index(edited_cave
);
2929 g_return_if_fail(i
!=-1);
2930 i
=(i
+1)%caveset
->caves
.size();
2931 select_cave_for_edit(&caveset
->cave(i
));
2934 /* create new cave */
2936 new_cave_cb(GtkWidget
*widget
, gpointer data
) {
2937 CaveStored
*newcave
;
2938 GtkWidget
*dialog
, *entry_name
, *entry_desc
, *intermission_check
, *table
;
2940 dialog
=gtk_dialog_new_with_buttons(_("Create New Cave"), GTK_WINDOW(gd_editor_window
), GtkDialogFlags(GTK_DIALOG_NO_SEPARATOR
| GTK_DIALOG_DESTROY_WITH_PARENT
), GTK_STOCK_CANCEL
, GTK_RESPONSE_REJECT
, GTK_STOCK_NEW
, GTK_RESPONSE_ACCEPT
, NULL
);
2941 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
2942 gtk_window_set_resizable(GTK_WINDOW(dialog
), FALSE
);
2944 table
=gtk_table_new(0, 0, FALSE
);
2945 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), table
, FALSE
, FALSE
, 0);
2946 gtk_container_set_border_width(GTK_CONTAINER (table
), 6);
2947 gtk_table_set_row_spacings(GTK_TABLE(table
), 6);
2948 gtk_table_set_col_spacings(GTK_TABLE(table
), 6);
2950 /* some properties - name */
2951 gtk_table_attach_defaults(GTK_TABLE(table
), gd_label_new_leftaligned (_("Name:")), 0, 1, 0, 1);
2952 entry_name
=gtk_entry_new();
2953 gtk_entry_set_activates_default(GTK_ENTRY(entry_name
), TRUE
);
2954 gtk_entry_set_text(GTK_ENTRY(entry_name
), _("New cave"));
2955 gtk_table_attach_defaults(GTK_TABLE(table
), entry_name
, 1, 2, 0, 1);
2958 gtk_table_attach_defaults(GTK_TABLE(table
), gd_label_new_leftaligned (_("Description:")), 0, 1, 1, 2);
2959 entry_desc
=gtk_entry_new();
2960 gtk_entry_set_activates_default(GTK_ENTRY(entry_desc
), TRUE
);
2961 gtk_table_attach_defaults(GTK_TABLE(table
), entry_desc
, 1, 2, 1, 2);
2964 gtk_table_attach_defaults(GTK_TABLE(table
), gd_label_new_leftaligned (_("Intermission:")), 0, 1, 2, 3);
2965 intermission_check
=gtk_check_button_new();
2966 gtk_widget_set_tooltip_text(intermission_check
, _("Intermission caves are usually small and fast caves, which are not required to be solved. The player will not lose a life if he is not successful. The game always proceeds to the next cave. If you set this check box, the size of the cave will also be set to 20x12, as that is the standard size for intermissions."));
2967 gtk_table_attach_defaults(GTK_TABLE(table
), intermission_check
, 1, 2, 2, 3);
2969 gtk_widget_show_all(dialog
);
2970 if (gtk_dialog_run(GTK_DIALOG(dialog
)) == GTK_RESPONSE_ACCEPT
) {
2972 newcave
=new CaveStored
;
2974 /* set some defaults */
2975 gd_cave_set_random_colors(*newcave
, GdColor::Type(gd_preferred_palette
));
2976 newcave
->name
=gtk_entry_get_text(GTK_ENTRY(entry_name
));
2977 newcave
->description
=gtk_entry_get_text(GTK_ENTRY(entry_desc
));
2978 newcave
->author
=g_get_real_name();
2979 newcave
->intermission
=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(intermission_check
));
2980 /* if the user says that he creates an intermission, it is immediately resized */
2981 if (newcave
->intermission
) {
2984 gd_cave_correct_visible_size(*newcave
);
2986 newcave
->date
=gd_get_current_date();
2988 select_cave_for_edit(newcave
); /* close caveset icon view, and show cave */
2989 caveset
->caves
.push_back_adopt(newcave
); /* append here, as the icon view may only be destroyed now by select_cave_for_edit */
2990 caveset
->edited
=TRUE
;
2992 gtk_widget_destroy(dialog
);
2997 * caveset file operations.
2998 * in each, we destroy the iconview, as it might store the modified order of caves!
2999 * then it is possible to load, save, and the like.
3000 * after any operation, activate caveset editor again
3003 open_caveset_cb(GtkWidget
*widget
, gpointer data
) {
3004 /* destroy icon view so it does not interfere */
3005 if (iconview_cavelist
)
3006 gtk_widget_destroy(iconview_cavelist
);
3007 g_hash_table_remove_all(cave_pixbufs
);
3008 gd_open_caveset(NULL
, *caveset
);
3009 select_cave_for_edit(NULL
);
3013 open_installed_caveset_cb(GtkWidget
*widget
, gpointer data
) {
3014 /* destroy icon view so it does not interfere */
3015 if (iconview_cavelist
)
3016 gtk_widget_destroy(iconview_cavelist
);
3017 g_hash_table_remove_all(cave_pixbufs
);
3018 gd_open_caveset(gd_system_caves_dir
, *caveset
);
3019 select_cave_for_edit(NULL
);
3023 save_caveset_as_cb(GtkWidget
*widget
, gpointer data
) {
3027 /* destroy icon view so it does not interfere with the cave order, and the order of caves is saved */
3028 if (iconview_cavelist
)
3029 gtk_widget_destroy(iconview_cavelist
);
3030 gd_save_caveset_as(*caveset
);
3031 select_cave_for_edit(edited
); /* go back to edited cave or recreate icon view */
3035 save_caveset_cb(GtkWidget
*widget
, gpointer data
) {
3039 /* destroy icon view so it does not interfere with the cave order, and the order of caves is saved */
3040 if (iconview_cavelist
)
3041 gtk_widget_destroy(iconview_cavelist
);
3042 gd_save_caveset(*caveset
);
3043 select_cave_for_edit(edited
); /* go back to edited cave or recreate icon view */
3047 new_caveset_cb(GtkWidget
*widget
, gpointer data
) {
3051 /* destroy icon view so it does not interfere */
3052 if (iconview_cavelist
)
3053 gtk_widget_destroy(iconview_cavelist
);
3054 if (gd_discard_changes(*caveset
)) {
3056 g_hash_table_remove_all(cave_pixbufs
);
3058 select_cave_for_edit(NULL
);
3061 g_date_set_time_t(date
, time(NULL
));
3062 g_date_strftime(datestr
, sizeof(datestr
), "%Y-%m-%d", date
);
3063 caveset
->date
=datestr
;
3066 caveset
->author
=g_get_real_name();
3068 caveset_properties(false); /* false=do not show cancel button */
3073 remove_all_unknown_tags_cb(GtkWidget
*widget
, gpointer data
) {
3074 /* remember which cave was edited, or null if icon view. */
3075 CaveStored
*edited
=edited_cave
;
3076 /* destroy icon view so it does not interfere with the cave order, and the order of caves is saved */
3077 if (iconview_cavelist
)
3078 gtk_widget_destroy(iconview_cavelist
);
3080 gboolean response
=gd_question_yesno(_("Do you really want to remove unknown cave tags?"), _("This operation removes all unknown tags associated with all caves. Unknown tags might come from another BDCFF-compatible game or an older version of GDash. Those cave options cannot be interpreted by GDash, and therefore if you use this caveset in this application, they are of no use."));
3083 for (unsigned n
=0; n
<caveset
->caves
.size(); ++n
)
3084 caveset
->cave(n
).unknown_tags
="";
3086 select_cave_for_edit(edited
); /* go back to edited cave or recreate icon view */
3090 /* make all caves selectable */
3092 selectable_all_cb(GtkWidget
*widget
, gpointer data
) {
3093 gtk_widget_destroy(iconview_cavelist
); /* to generate caveset */
3094 for (unsigned n
=0; n
<caveset
->caves
.size(); ++n
) {
3095 CaveStored
&cave
=caveset
->cave(n
);
3097 if (!cave
.selectable
) {
3098 cave
.selectable
=TRUE
;
3099 g_hash_table_remove(cave_pixbufs
, &cave
);
3102 select_cave_for_edit(NULL
);
3105 /* make all but intermissions selectable */
3107 selectable_all_but_intermissions_cb(GtkWidget
*widget
, gpointer data
) {
3108 gtk_widget_destroy(iconview_cavelist
); /* to generate caveset */
3109 for (unsigned n
=0; n
<caveset
->caves
.size(); ++n
) {
3110 CaveStored
&cave
=caveset
->cave(n
);
3111 gboolean desired
=!cave
.intermission
;
3113 if (cave
.selectable
!=desired
) {
3114 cave
.selectable
=desired
;
3115 g_hash_table_remove(cave_pixbufs
, &cave
);
3118 select_cave_for_edit(NULL
);
3122 /* make all after intermissions selectable */
3124 selectable_all_after_intermissions_cb(GtkWidget
*widget
, gpointer data
) {
3125 gboolean was_intermission
=TRUE
; /* treat the 'zeroth' cave as intermission, so the very first cave will be selectable */
3127 gtk_widget_destroy(iconview_cavelist
); /* to generate caveset */
3128 for (unsigned n
=0; n
<caveset
->caves
.size(); ++n
) {
3129 CaveStored
&cave
=caveset
->cave(n
);
3132 desired
=!cave
.intermission
&& was_intermission
; /* selectable if this is a normal cave, and the previous one was an interm. */
3133 if (cave
.selectable
!=desired
) {
3134 cave
.selectable
=desired
;
3135 g_hash_table_remove(cave_pixbufs
, &cave
);
3138 was_intermission
=cave
.intermission
; /* remember for next iteration */
3140 select_cave_for_edit(NULL
);
3144 /******************************************************
3146 * some necessary callbacks for the editor
3151 /* level shown in editor */
3153 level_scale_changed_cb(GtkWidget
*widget
, gpointer data
) {
3154 edit_level
=gtk_range_get_value (GTK_RANGE (widget
))-1;
3155 render_cave(); /* re-render cave */
3158 /* new objects are created on this level - combo change updates variable */
3160 new_object_combo_changed_cb(GtkWidget
*widget
, gpointer data
) {
3161 gtk_range_set_value(GTK_RANGE(level_scale
), new_objects_visible_on
[gtk_combo_box_get_active(GTK_COMBO_BOX(widget
))].switch_to_level
);
3165 /* edit tool selected */
3166 static void action_select_tool_cb(GtkWidget
*widget
, gpointer data
) {
3167 action
=EditTool(gtk_radio_action_get_current_value(GTK_RADIO_ACTION (widget
)));
3169 /* first button - mainly for draw */
3170 gtk_label_set_text(GTK_LABEL(label_first_element
), _(gd_object_description
[action
].first_button
));
3171 gtk_widget_set_sensitive(element_button
, gd_object_description
[action
].first_button
!=NULL
);
3172 gd_element_button_set_dialog_sensitive(element_button
, gd_object_description
[action
].first_button
!=NULL
);
3173 if (gd_object_description
[action
].first_button
) {
3174 char *title
=g_strdup_printf(_("%s Element"), _(gd_object_description
[action
].first_button
));
3175 gd_element_button_set_dialog_title(element_button
, title
);
3178 gd_element_button_set_dialog_title(element_button
, _("Draw Element"));
3180 /* second button - mainly for fill */
3181 gtk_label_set_text(GTK_LABEL(label_second_element
), _(gd_object_description
[action
].second_button
));
3182 gtk_widget_set_sensitive(fillelement_button
, gd_object_description
[action
].second_button
!=NULL
);
3183 gd_element_button_set_dialog_sensitive(fillelement_button
, gd_object_description
[action
].second_button
!=NULL
);
3184 if (gd_object_description
[action
].second_button
) {
3185 char *title
=g_strdup_printf(_("%s Element"), _(gd_object_description
[action
].second_button
));
3186 gd_element_button_set_dialog_title(fillelement_button
, title
);
3189 gd_element_button_set_dialog_title(fillelement_button
, _("Fill Element"));
3193 toggle_game_view_cb(GtkWidget
*widget
, gpointer data
) {
3194 gd_game_view
=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget
));
3198 toggle_colored_objects_cb(GtkWidget
*widget
, gpointer data
) {
3199 gd_colored_objects
=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget
));
3203 toggle_object_list_cb(GtkWidget
*widget
, gpointer data
) {
3204 gd_show_object_list
=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION (widget
));
3205 if (gd_show_object_list
&& edited_cave
)
3206 gtk_widget_show(scroll_window_objects
); /* show the scroll window containing the view */
3208 gtk_widget_hide(scroll_window_objects
);
3212 toggle_test_label_cb(GtkWidget
*widget
, gpointer data
) {
3213 gd_show_test_label
=gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(widget
));
3218 close_editor_cb(GtkWidget
*widget
, gpointer data
) {
3224 remove_bad_replays_cb(GtkWidget
*widget
, gpointer data
) {
3225 /* if the iconview is active, we destroy it, as it interferes with GList *caveset.
3226 after highscore editing, we call editcave(null) to recreate */
3227 gboolean has_iconview
=iconview_cavelist
!=NULL
;
3232 gtk_widget_destroy(iconview_cavelist
);
3234 report
=g_string_new(NULL
);
3236 for (unsigned n
=0; n
<caveset
->caves
.size(); n
++) {
3237 CaveStored
&cave
=caveset
->cave(n
);
3240 removed
=gd_cave_check_replays(cave
, FALSE
, TRUE
, FALSE
);
3243 g_string_append_printf(report
, _("%s: removed %d replay(s)\n"), cave
.name
.c_str(), removed
);
3247 select_cave_for_edit(NULL
);
3249 gd_infomessage(_("Some replays were removed."), report
->str
);
3250 caveset
->edited
=TRUE
;
3252 g_string_free(report
, TRUE
);
3257 mark_all_replays_as_working_cb(GtkWidget
*widget
, gpointer data
) {
3258 /* if the iconview is active, we destroy it, as it interferes with GList *caveset. */
3259 gboolean has_iconview
=iconview_cavelist
!=NULL
;
3264 gtk_widget_destroy(iconview_cavelist
);
3266 report
=g_string_new(NULL
);
3268 for (unsigned n
=0; n
<caveset
->caves
.size(); ++n
) {
3269 CaveStored
&cave
=caveset
->cave(n
);
3272 changed
=gd_cave_check_replays(cave
, FALSE
, FALSE
, TRUE
);
3275 g_string_append_printf(report
, _("%s: marked %d replay(s) as working ones\n"), cave
.name
.c_str(), changed
);
3279 select_cave_for_edit(NULL
);
3281 gd_warningmessage(_("Some replay checksums were recalculated. This does not mean that those replays actually play correctly!"), report
->str
);
3282 caveset
->edited
=TRUE
;
3284 g_string_free(report
, TRUE
);
3288 /* set image from gd_caveset_data title screen. */
3289 static void setup_caveset_title_image_load_image(GtkWidget
*image
) {
3290 if (caveset
->title_screen
!="") {
3291 std::vector
<Pixbuf
*> title_images
= get_title_animation_pixbuf(caveset
->title_screen
, caveset
->title_screen_scroll
, true, *editor_pixbuf_factory
);
3292 if (!title_images
.empty()) {
3293 GdkPixbuf
*bigone
=static_cast<GTKPixbuf
*>(title_images
[0])->get_gdk_pixbuf();
3294 gtk_image_set_from_pixbuf(GTK_IMAGE(image
), bigone
);
3295 delete title_images
[0];
3297 // could not load, so clear it
3298 caveset
->title_screen
="";
3299 caveset
->title_screen_scroll
="";
3300 gtk_image_clear(GTK_IMAGE(image
));
3303 gtk_image_clear(GTK_IMAGE(image
));
3307 /* forgets caveset title image. */
3308 static void setup_caveset_title_image_clear_cb(GtkWidget
*widget
, gpointer data
) {
3309 GtkWidget
*image
=(GtkWidget
*)data
;
3311 caveset
->title_screen
="";
3312 caveset
->title_screen_scroll
="";
3313 setup_caveset_title_image_load_image(image
);
3316 /* load image from disk */
3318 setup_caveset_title_image_load_image_into_string(const char *title
, GtkWidget
*parent
, GtkWidget
*image
, std::string
&string
, int maxwidth
, int maxheight
) {
3327 filename
=gd_select_image_file(title
);
3328 if (!filename
) /* no filename -> do nothing */
3331 /* check image format and size */
3332 if (!gdk_pixbuf_get_file_info(filename
, &width
, &height
)) {
3333 gd_errormessage(_("Error loading image file."), _("Cannot recognize file format."));
3336 /* check for maximum size */
3337 if (height
>maxheight
|| width
>maxwidth
) {
3338 gd_errormessage(_("The image selected is too big!"), CPrintf(_("Maximum sizes: %dx%d pixels")) % maxwidth
% maxheight
);
3343 pixbuf
=gdk_pixbuf_new_from_file(filename
, &error
);
3345 /* cannot load image - do nothing */
3346 gd_errormessage(_("Error loading image file."), error
->message
);
3347 g_error_free(error
);
3351 /* now the image is loaded, "save" as png into a buffer */
3352 gdk_pixbuf_save_to_buffer(pixbuf
, &buffer
, &bufsize
, "png", &error
, "compression", "9", NULL
);
3353 g_object_unref(pixbuf
); /* not needed anymore */
3355 /* cannot load image - do nothing */
3356 gd_errormessage("Internal error: %s", error
->message
);
3357 g_error_free(error
);
3360 base64
=g_base64_encode((guchar
*) buffer
, bufsize
);
3361 g_free(buffer
); /* binary data can be freed */
3363 g_free(base64
); /* copied to string so can be freed */
3364 setup_caveset_title_image_load_image(image
);
3365 caveset
->edited
=TRUE
;
3369 setup_caveset_title_image_load_screen_cb(GtkWidget
*widget
, gpointer data
) {
3370 setup_caveset_title_image_load_image_into_string(_("Select Image File for Title Screen"), gtk_widget_get_toplevel(widget
),
3371 (GtkWidget
*)data
, caveset
->title_screen
, GD_TITLE_SCREEN_MAX_WIDTH
, GD_TITLE_SCREEN_MAX_HEIGHT
);
3375 setup_caveset_title_image_load_tile_cb(GtkWidget
*widget
, gpointer data
) {
3376 setup_caveset_title_image_load_image_into_string(_("Select Image File for Background Tile"), gtk_widget_get_toplevel(widget
),
3377 (GtkWidget
*)data
, caveset
->title_screen_scroll
, GD_TITLE_SCROLL_MAX_WIDTH
, GD_TITLE_SCROLL_MAX_HEIGHT
);
3380 /* load images for the caveset title screen */
3382 setup_caveset_title_image_cb(GtkWidget
*widget
, gpointer data
) {
3384 GtkWidget
*frame
, *align
, *image
, *bbox
;
3385 GtkWidget
*setbutton
, *settilebutton
, *clearbutton
;
3388 dialog
=gtk_dialog_new_with_buttons(_("Set Title Image"), GTK_WINDOW(gd_editor_window
), GtkDialogFlags(0),
3389 GTK_STOCK_OK
, GTK_RESPONSE_ACCEPT
, NULL
);
3391 /* an align, so the image shrinks to its size, and not the vbox determines its width. */
3392 align
=gtk_alignment_new(0.5, 0.5, 0, 0);
3393 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), align
, TRUE
, TRUE
, 6);
3394 /* a frame around the image */
3395 frame
=gtk_frame_new(NULL
);
3396 gtk_container_add(GTK_CONTAINER(align
), frame
);
3397 image
=gtk_image_new();
3398 gtk_widget_set_size_request(image
, GD_TITLE_SCREEN_MAX_WIDTH
, GD_TITLE_SCREEN_MAX_HEIGHT
); /* max title image size */
3399 gtk_container_add(GTK_CONTAINER(frame
), image
);
3401 setup_caveset_title_image_load_image(image
);
3402 bbox
=gtk_hbutton_box_new();
3403 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog
)->vbox
), bbox
, FALSE
, FALSE
, 6);
3404 gtk_container_add(GTK_CONTAINER(bbox
), setbutton
=gtk_button_new_with_mnemonic(_("Load _image")));
3405 gtk_container_add(GTK_CONTAINER(bbox
), settilebutton
=gtk_button_new_with_mnemonic(_("Load _tile")));
3406 gtk_container_add(GTK_CONTAINER(bbox
), clearbutton
=gtk_button_new_from_stock(GTK_STOCK_CLEAR
));
3407 g_signal_connect(clearbutton
, "clicked", G_CALLBACK(setup_caveset_title_image_clear_cb
), image
);
3408 g_signal_connect(setbutton
, "clicked", G_CALLBACK(setup_caveset_title_image_load_screen_cb
), image
);
3409 g_signal_connect(settilebutton
, "clicked", G_CALLBACK(setup_caveset_title_image_load_tile_cb
), image
);
3411 hint
=g_strdup_printf(_("Recommended image sizes are 320x176 pixels for title image and 8x8 pixels for the scrolling tile. Maximum sizes are %dx%d and %dx%d, respectively."), GD_TITLE_SCREEN_MAX_WIDTH
, GD_TITLE_SCREEN_MAX_HEIGHT
, GD_TITLE_SCROLL_MAX_WIDTH
, GD_TITLE_SCROLL_MAX_HEIGHT
);
3412 gd_dialog_add_hint(GTK_DIALOG(dialog
), hint
);
3415 gtk_widget_show_all(dialog
);
3416 gtk_dialog_run(GTK_DIALOG(dialog
));
3417 gtk_widget_destroy(dialog
);
3422 create_lowercase_element_names() {
3424 static bool lowercase_names_created
=false;
3425 if (lowercase_names_created
)
3427 lowercase_names_created
=true;
3429 bool lowercase_names
=true;
3430 /* TRANSLATORS: in some languages (for example, german) nouns must be written capitalized.
3431 * In other languages, nouns can be written uncapitalized.
3432 * When the name of an element is put in a sentence, this has to be taken into account.
3434 * For example, the element name in English is "Brick wall",
3435 * and it is possible to write "Line of brick wall" (uppercase B -> lowercase b in the sentence).
3436 * In German, the same is "Ziegelmauer", and "Linie aus Ziegelmauer" (Z remains in upper case).
3438 * If the language you are translating to writes nouns capitalized (for example, German),
3439 * translate this string to "lowercase-element-names-no".
3440 * Otherwise translate this to "lowercase-element-values-yes".
3442 if (gd_str_equal(gettext("lowercase-element-names-yes"), "lowercase-element-names-no"))
3443 lowercase_names
=false;
3444 for (int i
=0; gd_element_properties
[i
].element
!=-1; ++i
) {
3445 /* if it has a name, create a lowercase name (of the translated one). will be used by the editor */
3446 if (gd_element_properties
[i
].visiblename
) {
3447 if (lowercase_names
)
3448 /* the function allocates a new string, but it is needed as long as the app is running */
3449 gd_element_properties
[i
].lowercase_name
=
3450 gd_tostring_free(g_utf8_strdown(gettext(gd_element_properties
[i
].visiblename
), -1));
3452 /* only translate, no lowercase. */
3453 gd_element_properties
[i
].lowercase_name
= gettext(gd_element_properties
[i
].visiblename
);
3459 static void preferences_cb(GtkWidget
*widget
, gpointer data
) {
3460 GTKPixbufFactory pf
;
3461 SettingsWindow::do_settings_dialog(gd_get_game_settings_array(), pf
);
3465 /* normal menu items */
3466 static GtkActionEntry action_entries_normal
[]={
3467 {"FileMenu", NULL
, N_("_File")},
3468 {"EditMenu", NULL
, N_("_Edit")},
3469 {"ViewMenu", NULL
, N_("_View")},
3470 {"ToolsMenu", NULL
, N_("_Tools")},
3471 {"HelpMenu", NULL
, N_("_Help")},
3472 {"ObjectMenu", NULL
, N_("_Object")},
3473 {"CaveMenu", NULL
, N_("_Cave")},
3474 {"CaveSetMenu", NULL
, N_("Cave_set")},
3475 {"Close", GTK_STOCK_CLOSE
, NULL
, NULL
, N_("Close cave editor"), G_CALLBACK(close_editor_cb
)},
3476 {"NewCave", GTK_STOCK_NEW
, N_("New _cave"), NULL
, N_("Create new cave"), G_CALLBACK(new_cave_cb
)},
3477 {"Help", GTK_STOCK_HELP
, NULL
, NULL
, NULL
, G_CALLBACK(help_cb
)},
3478 {"SaveFile", GTK_STOCK_SAVE
, NULL
, NULL
, N_("Save cave set to file"), G_CALLBACK(save_caveset_cb
)},
3479 {"SaveAsFile", GTK_STOCK_SAVE_AS
, NULL
, NULL
, N_("Save cave set as new file"), G_CALLBACK(save_caveset_as_cb
)},
3480 {"OpenFile", GTK_STOCK_OPEN
, NULL
, NULL
, N_("Load cave set from file"), G_CALLBACK(open_caveset_cb
)},
3481 {"OpenInstalledFile", GTK_STOCK_CDROM
, N_("O_pen shipped"), NULL
, N_("Load shipped cave set from file"), G_CALLBACK(open_installed_caveset_cb
)},
3482 {"SelectAll", GTK_STOCK_SELECT_ALL
, NULL
, "<control>A", N_("Select all items"), G_CALLBACK(select_all_cb
)},
3483 {"CaveSetProps", GTK_STOCK_PROPERTIES
, N_("Cave set _properties"), NULL
, N_("Set properties of cave set"), G_CALLBACK(set_caveset_properties_cb
)},
3484 {"CaveSetTitleImage", GD_ICON_IMAGE
, N_("Cave set _title image"), NULL
, N_("Set caveset title image"), G_CALLBACK(setup_caveset_title_image_cb
)},
3485 {"Preferences", GTK_STOCK_PREFERENCES
, NULL
, NULL
, NULL
, G_CALLBACK(preferences_cb
)},
3488 /* cave_selector menu items */
3489 static const GtkActionEntry action_entries_cave_selector
[]={
3490 {"CaveRoleMenu", NULL
, N_("_Role in caveset")},
3491 {"EditCave", GD_ICON_CAVE_EDITOR
, N_("_Edit cave"), NULL
, N_("Edit selected cave"), G_CALLBACK(icon_view_edit_cave_cb
)},
3492 {"RenameCave", NULL
, N_("_Rename cave"), NULL
, N_("Rename selected cave"), G_CALLBACK(icon_view_rename_cave_cb
)},
3493 {"MakeSelectable", NULL
, N_("Make cave _selectable"), NULL
, N_("Make the cave selectable as game start"), G_CALLBACK(icon_view_cave_make_selectable_cb
)},
3494 {"MakeUnselectable", NULL
, N_("Make cave _unselectable"), NULL
, N_("Make the cave unselectable as game start"), G_CALLBACK(icon_view_cave_make_unselectable_cb
)},
3497 /* caveset editing */
3498 static const GtkActionEntry action_entries_edit_caveset
[]={
3499 {"NewCaveset", GTK_STOCK_NEW
, N_("_New cave set"), "", N_("Create new cave set with no caves"), G_CALLBACK(new_caveset_cb
)},
3500 {"SaveHTML", GTK_STOCK_FILE
, N_("Save _HTML gallery"), NULL
, N_("Save game in a HTML gallery"), G_CALLBACK(save_html_cb
)},
3501 {"ExportCavePack", GTK_STOCK_CONVERT
, N_("Export _CrLi cave pack"), NULL
, NULL
, G_CALLBACK(export_cavepack_cb
)},
3502 {"SelectableMenu", NULL
, N_("_Selectable caves")},
3503 {"AllCavesSelectable", NULL
, N_("All _caves"), NULL
, N_("Make all caves selectable as game start"), G_CALLBACK(selectable_all_cb
)},
3504 {"AllButIntermissionsSelectable", NULL
, N_("All _but intermissions"), NULL
, N_("Make all caves but intermissions selectable as game start"), G_CALLBACK(selectable_all_but_intermissions_cb
)},
3505 {"AllAfterIntermissionsSelectable", NULL
, N_("All _after intermissions"), NULL
, N_("Make all caves after intermissions selectable as game start"), G_CALLBACK(selectable_all_after_intermissions_cb
)},
3506 {"RemoveAllUnknownTags", NULL
, N_("Remove all unknown tags"), NULL
, N_("Removes all unknown tags found in the BDCFF file"), G_CALLBACK(remove_all_unknown_tags_cb
)},
3507 {"RemoveBadReplays", NULL
, N_("Remove bad replays"), NULL
, N_("Removes replays which won't play as they have their caves modified."), G_CALLBACK(remove_bad_replays_cb
)},
3508 {"MarkAllReplaysAsWorking", NULL
, N_("Fix replay checksums"), NULL
, N_("Treats all replays with wrong checksums as working ones."), G_CALLBACK(mark_all_replays_as_working_cb
)},
3511 /* cave editing menu items */
3512 static const GtkActionEntry action_entries_edit_cave
[]={
3513 {"MapMenu", NULL
, N_("_Map")},
3514 {"ExportAsCrLiCave", GTK_STOCK_CONVERT
, N_("_Export as CrLi cave file"), NULL
, NULL
, G_CALLBACK(export_cavefile_cb
)},
3515 {"CaveSelector", GTK_STOCK_INDEX
, NULL
, "Escape", N_("Open cave selector"), G_CALLBACK(cave_selector_cb
)},
3516 {"NextCave", GTK_STOCK_GO_FORWARD
, N_("_Next cave"), "Page_Down", N_("Next cave"), G_CALLBACK(next_cave_cb
)},
3517 {"PreviousCave", GTK_STOCK_GO_BACK
, N_("_Previous cave"), "Page_Up", N_("Previous cave"), G_CALLBACK(previous_cave_cb
)},
3518 {"Test", GTK_STOCK_MEDIA_PLAY
, N_("_Test"), "<control>T", N_("Test cave"), G_CALLBACK(play_level_cb
)},
3519 {"CaveProperties", GTK_STOCK_PROPERTIES
, N_("Ca_ve properties"), NULL
, N_("Cave settings"), G_CALLBACK(cave_properties_cb
)},
3520 {"EngineDefaults", NULL
, N_("Set engine defaults")},
3521 {"CaveColors", GTK_STOCK_SELECT_COLOR
, N_("Cave co_lors"), NULL
, N_("Select cave colors"), G_CALLBACK(cave_colors_cb
)},
3522 {"FlattenCave", NULL
, N_("Convert to map"), NULL
, N_("Flatten cave to a single cave map without objects"), G_CALLBACK(flatten_cave_cb
)},
3523 {"Overview", GTK_STOCK_ZOOM_FIT
, N_("O_verview"), NULL
, N_("Full screen overview of cave"), G_CALLBACK(cave_overview_cb
)},
3524 {"OverviewSimple", GTK_STOCK_ZOOM_FIT
, N_("O_verview (simple)"), NULL
, N_("Full screen overview of cave almost as in game"), G_CALLBACK(cave_overview_simple_cb
)},
3525 {"AutoShrink", NULL
, N_("_Auto shrink"), NULL
, N_("Automatically set the visible region of the cave"), G_CALLBACK(auto_shrink_cave_cb
)},
3528 /* action entries which relate to a selected cave element (line, rectangle...) */
3529 static const GtkActionEntry action_entries_edit_object
[]={
3530 {"SendToBack", GD_ICON_TO_BOTTOM
, N_("Send to _back"), "<control>End", N_("Send object to bottom of object list (draw first)"), G_CALLBACK(send_to_back_selected_cb
)},
3531 {"BringToFront", GD_ICON_TO_TOP
, N_("Bring to _front"), "<control>Home", N_("Bring object to front of object list (drawn last)"), G_CALLBACK(bring_to_front_selected_cb
)},
3532 {"ShowOnThisLevel", GTK_STOCK_ADD
, N_("Show on this level"), NULL
, N_("Enable object on currently visible level"), G_CALLBACK(show_object_on_this_level_cb
)},
3533 {"HideOnThisLevel", GTK_STOCK_REMOVE
, N_("Hide on this level"), NULL
, N_("Disable object on currently visible level"), G_CALLBACK(hide_object_on_this_level_cb
)},
3534 {"OnlyOnThisLevel", NULL
, N_("Only on this level"), NULL
, N_("Enable object only on the currently visible level"), G_CALLBACK(show_object_this_level_only_cb
)},
3535 {"ShowOnAllLevels", NULL
, N_("Show on all levels"), NULL
, N_("Enable object on all levels"), G_CALLBACK(show_object_all_levels_cb
)},
3538 static const GtkActionEntry action_entries_edit_one_object
[]={
3539 {"ObjectProperties", GTK_STOCK_PREFERENCES
, N_("Ob_ject properties"), NULL
, N_("Set object properties"), G_CALLBACK(object_properties_cb
)},
3543 static const GtkActionEntry action_entries_edit_map
[]={
3544 {"ShiftLeft", GTK_STOCK_GO_BACK
, N_("Shift _left"), NULL
, NULL
, G_CALLBACK(shift_left_cb
)},
3545 {"ShiftRight", GTK_STOCK_GO_FORWARD
, N_("Shift _right"), NULL
, NULL
, G_CALLBACK(shift_right_cb
)},
3546 {"ShiftUp", GTK_STOCK_GO_UP
, N_("Shift _up"), NULL
, NULL
, G_CALLBACK(shift_up_cb
)},
3547 {"ShiftDown", GTK_STOCK_GO_DOWN
, N_("Shift _down"), NULL
, NULL
, G_CALLBACK(shift_down_cb
)},
3548 {"RemoveMap", GTK_STOCK_CLEAR
, N_("Remove m_ap"), NULL
, N_("Remove cave map, if it has one"), G_CALLBACK(remove_map_cb
)},
3551 /* random element actions */
3552 static const GtkActionEntry action_entries_edit_random
[]={
3553 {"SetupRandom", GD_ICON_RANDOM_FILL
, N_("Setup cave _random fill"), NULL
, N_("Setup initial fill random elements for the cave"), G_CALLBACK(cave_random_setup_cb
)},
3556 /* clipboard actions */
3557 static const GtkActionEntry action_entries_clipboard
[]={
3558 {"Cut", GTK_STOCK_CUT
, NULL
, NULL
, N_("Cut to clipboard"), G_CALLBACK(cut_selected_cb
)},
3559 {"Copy", GTK_STOCK_COPY
, NULL
, NULL
, N_("Copy to clipboard"), G_CALLBACK(copy_selected_cb
)},
3560 {"Delete", GTK_STOCK_DELETE
, NULL
, "Delete", N_("Delete"), G_CALLBACK(delete_selected_cb
)},
3563 /* clipboard paste */
3564 static const GtkActionEntry action_entries_clipboard_paste
[]={
3565 {"Paste", GTK_STOCK_PASTE
, NULL
, NULL
, N_("Paste object from clipboard"), G_CALLBACK(paste_clipboard_cb
)},
3568 /* action entries for undo */
3569 static const GtkActionEntry action_entries_edit_undo
[]={
3570 {"Undo", GTK_STOCK_UNDO
, NULL
, "<control>Z", N_("Undo last action"), G_CALLBACK(undo_cb
)},
3573 /* action entries for redo */
3574 static const GtkActionEntry action_entries_edit_redo
[]={
3575 {"Redo", GTK_STOCK_REDO
, NULL
, "<control><shift>Z", N_("Redo last action"), G_CALLBACK(redo_cb
)},
3579 * Start cave editor.
3581 static void create_cave_editor(CaveSet
*cs
) {
3582 /* toggle buttons: nonstatic as they use values from settings */
3583 /* also cannot make this global! */
3584 const GtkToggleActionEntry action_entries_toggle
[]={
3585 {"SimpleView", NULL
, N_("_Animated view"), NULL
, N_("Animated view"), G_CALLBACK(toggle_game_view_cb
), gd_game_view
},
3586 {"ColoredObjects", NULL
, N_("_Colored objects"), NULL
, N_("Cave objects are colored"), G_CALLBACK(toggle_colored_objects_cb
), gd_colored_objects
},
3587 {"ShowObjectList", GTK_STOCK_INDEX
, N_("_Object list"), "F9", N_("Object list sidebar"), G_CALLBACK(toggle_object_list_cb
), gd_show_object_list
},
3588 {"ShowTestLabel", GTK_STOCK_INDEX
, N_("_Show variables in test"), NULL
, N_("Show a label during tests with some cave parameters"), G_CALLBACK(toggle_test_label_cb
), gd_show_test_label
}
3591 static const char *ui_info
=
3594 "<popup name='DrawingAreaPopup'>"
3595 "<menuitem action='Undo'/>"
3596 "<menuitem action='Redo'/>"
3598 "<menuitem action='Cut'/>"
3599 "<menuitem action='Copy'/>"
3600 "<menuitem action='Paste'/>"
3601 "<menuitem action='Delete'/>"
3603 "<menuitem action='BringToFront'/>"
3604 "<menuitem action='SendToBack'/>"
3605 "<menuitem action='ShowOnThisLevel'/>"
3606 "<menuitem action='HideOnThisLevel'/>"
3607 "<menuitem action='OnlyOnThisLevel'/>"
3608 "<menuitem action='ShowOnAllLevels'/>"
3609 "<menuitem action='ObjectProperties'/>"
3611 "<menuitem action='CaveProperties'/>"
3614 "<popup name='ObjectListPopup'>"
3615 "<menuitem action='Cut'/>"
3616 "<menuitem action='Copy'/>"
3617 "<menuitem action='Paste'/>"
3618 "<menuitem action='Delete'/>"
3620 "<menuitem action='BringToFront'/>"
3621 "<menuitem action='SendToBack'/>"
3622 "<menuitem action='ShowOnThisLevel'/>"
3623 "<menuitem action='HideOnThisLevel'/>"
3624 "<menuitem action='OnlyOnThisLevel'/>"
3625 "<menuitem action='ShowOnAllLevels'/>"
3626 "<menuitem action='ObjectProperties'/>"
3629 "<popup name='CavesetPopup'>"
3630 "<menuitem action='Cut'/>"
3631 "<menuitem action='Copy'/>"
3632 "<menuitem action='Paste'/>"
3633 "<menuitem action='Delete'/>"
3635 "<menuitem action='NewCave'/>"
3636 "<menuitem action='EditCave'/>"
3637 "<menuitem action='RenameCave'/>"
3638 "<menuitem action='MakeSelectable'/>"
3639 "<menuitem action='MakeUnselectable'/>"
3642 "<menubar name='MenuBar'>"
3643 "<menu action='FileMenu'>"
3644 "<menuitem action='NewCave'/>"
3645 "<menuitem action='NewCaveset'/>"
3647 "<menuitem action='OpenFile'/>"
3648 "<menuitem action='OpenInstalledFile'/>"
3650 "<menuitem action='SaveFile'/>"
3651 "<menuitem action='SaveAsFile'/>"
3653 "<menuitem action='ExportCavePack'/>"
3654 "<menuitem action='ExportAsCrLiCave'/>"
3655 "<menuitem action='SaveHTML'/>"
3657 "<menuitem action='Close'/>"
3659 "<menu action='EditMenu'>"
3660 "<menuitem action='Undo'/>"
3661 "<menuitem action='Redo'/>"
3663 "<menuitem action='Cut'/>"
3664 "<menuitem action='Copy'/>"
3665 "<menuitem action='Paste'/>"
3666 "<menuitem action='Delete'/>"
3668 "<menuitem action='SelectAll'/>"
3670 "<menu action='ObjectMenu'>"
3671 "<menuitem action='BringToFront'/>"
3672 "<menuitem action='SendToBack'/>"
3673 "<menuitem action='ShowOnThisLevel'/>"
3674 "<menuitem action='HideOnThisLevel'/>"
3675 "<menuitem action='OnlyOnThisLevel'/>"
3676 "<menuitem action='ShowOnAllLevels'/>"
3678 "<menuitem action='ObjectProperties'/>"
3680 "<menu action='CaveMenu'>"
3681 "<menu action='CaveRoleMenu'>"
3682 "<menuitem action='RenameCave'/>"
3683 "<menuitem action='MakeSelectable'/>"
3684 "<menuitem action='MakeUnselectable'/>"
3686 "<menuitem action='SetupRandom'/>"
3687 "<menuitem action='CaveColors'/>"
3688 "<menu action='MapMenu'>"
3689 "<menuitem action='ShiftLeft'/>"
3690 "<menuitem action='ShiftRight'/>"
3691 "<menuitem action='ShiftUp'/>"
3692 "<menuitem action='ShiftDown'/>"
3694 "<menuitem action='FlattenCave'/>"
3695 "<menuitem action='RemoveMap'/>"
3698 "<menuitem action='EngineDefaults'/>"
3699 "<menuitem action='CaveProperties'/>"
3701 "<menu action='CaveSetMenu'>"
3702 "<menuitem action='CaveSetTitleImage'/>"
3703 "<menu action='SelectableMenu'>"
3704 "<menuitem action='AllCavesSelectable'/>"
3705 "<menuitem action='AllButIntermissionsSelectable'/>"
3706 "<menuitem action='AllAfterIntermissionsSelectable'/>"
3709 "<menuitem action='RemoveAllUnknownTags'/>"
3710 "<menuitem action='RemoveBadReplays'/>"
3711 "<menuitem action='MarkAllReplaysAsWorking'/>"
3713 "<menuitem action='CaveSetProps'/>"
3716 "<menuitem action='Preferences'/>"
3718 "<menu action='ViewMenu'>"
3719 "<menuitem action='EditCave'/>"
3720 "<menuitem action='PreviousCave'/>"
3721 "<menuitem action='NextCave'/>"
3722 "<menuitem action='CaveSelector'/>"
3724 "<menuitem action='Overview'/>"
3725 "<menuitem action='OverviewSimple'/>"
3727 "<menuitem action='SimpleView'/>"
3728 "<menuitem action='ColoredObjects'/>"
3729 "<menuitem action='ShowObjectList'/>"
3730 "<menuitem action='ShowTestLabel'/>"
3732 "<menu action='ToolsMenu'>"
3733 "<menuitem action='Test'/>"
3735 "<menuitem action='Move'/>"
3736 "<menuitem action='Plot'/>"
3737 "<menuitem action='Freehand'/>"
3738 "<menuitem action='Line'/>"
3739 "<menuitem action='Rectangle'/>"
3740 "<menuitem action='FilledRectangle'/>"
3741 "<menuitem action='Raster'/>"
3742 "<menuitem action='Join'/>"
3743 "<menuitem action='FloodFillBorder'/>"
3744 "<menuitem action='FloodFillReplace'/>"
3745 "<menuitem action='RandomFill'/>"
3746 "<menuitem action='Maze'/>"
3747 "<menuitem action='UnicursalMaze'/>"
3748 "<menuitem action='BraidMaze'/>"
3749 "<menuitem action='CopyPaste'/>"
3751 "<menuitem action='Visible'/>"
3752 "<menuitem action='AutoShrink'/>"
3754 "<menu action='HelpMenu'>"
3755 "<menuitem action='Help'/>"
3759 "<toolbar name='ToolBar'>"
3760 "<toolitem action='CaveSelector'/>"
3761 "<toolitem action='PreviousCave'/>"
3762 "<toolitem action='NextCave'/>"
3764 "<toolitem action='ObjectProperties'/>"
3765 "<toolitem action='CaveProperties'/>"
3767 "<toolitem action='Move'/>"
3768 "<toolitem action='Plot'/>"
3769 "<toolitem action='Freehand'/>"
3770 "<toolitem action='Line'/>"
3771 "<toolitem action='Rectangle'/>"
3772 "<toolitem action='FilledRectangle'/>"
3773 "<toolitem action='Raster'/>"
3774 "<toolitem action='Join'/>"
3775 "<toolitem action='FloodFillBorder'/>"
3776 "<toolitem action='FloodFillReplace'/>"
3777 "<toolitem action='RandomFill'/>"
3778 "<toolitem action='Maze'/>"
3779 "<toolitem action='UnicursalMaze'/>"
3780 "<toolitem action='BraidMaze'/>"
3781 "<toolitem action='CopyPaste'/>"
3783 "<toolitem action='Test'/>"
3786 GtkWidget
*vbox
, *hbox
;
3788 GtkWidget
*hbox_combo
;
3789 GtkTreeViewColumn
*column
;
3790 GtkCellRenderer
*renderer
;
3793 if (gd_editor_window
) {
3794 /* if exists, only show it to the user. */
3795 gtk_window_present(GTK_WINDOW(gd_editor_window
));
3801 create_lowercase_element_names();
3803 /* hash table which stores cave pointer -> pixbufs. deleting a pixbuf calls g_object_unref. */
3804 cave_pixbufs
=g_hash_table_new_full(g_direct_hash
, g_direct_equal
, NULL
, g_object_unref
);
3806 gd_editor_window
=gtk_window_new(GTK_WINDOW_TOPLEVEL
);
3807 gtk_window_set_default_size(GTK_WINDOW(gd_editor_window
), gd_editor_window_width
, gd_editor_window_height
);
3808 g_signal_connect(G_OBJECT(gd_editor_window
), "destroy", G_CALLBACK(gtk_widget_destroyed
), &gd_editor_window
);
3809 g_signal_connect(G_OBJECT(gd_editor_window
), "destroy", G_CALLBACK(editor_window_destroy_event
), NULL
);
3810 g_signal_connect(G_OBJECT(gd_editor_window
), "delete-event", G_CALLBACK(editor_window_delete_event
), NULL
);
3812 editor_pixbuf_factory
=new GTKPixbufFactory(GdScalingType(gd_cell_scale_editor
), gd_pal_emulation_editor
);
3813 editor_cell_renderer
=new EditorCellRenderer(*editor_pixbuf_factory
, gd_theme
);
3815 vbox
=gtk_vbox_new(FALSE
, 0);
3816 gtk_container_add(GTK_CONTAINER (gd_editor_window
), vbox
);
3818 /* menu and toolbar */
3819 actions_edit_tools
=gtk_action_group_new("edit_tools");
3820 gtk_action_group_set_translation_domain (actions_edit_tools
, PACKAGE
);
3821 gtk_action_group_add_radio_actions (actions_edit_tools
, action_objects
, G_N_ELEMENTS(action_objects
), -1, G_CALLBACK(action_select_tool_cb
), NULL
);
3823 actions
=gtk_action_group_new("edit_normal");
3824 gtk_action_group_set_translation_domain (actions
, PACKAGE
);
3825 gtk_action_group_add_actions (actions
, action_entries_normal
, G_N_ELEMENTS(action_entries_normal
), NULL
);
3827 actions_edit_object
=gtk_action_group_new("edit_object");
3828 gtk_action_group_set_translation_domain (actions_edit_object
, PACKAGE
);
3829 gtk_action_group_add_actions (actions_edit_object
, action_entries_edit_object
, G_N_ELEMENTS(action_entries_edit_object
), NULL
);
3831 actions_edit_one_object
=gtk_action_group_new("edit_one_object");
3832 gtk_action_group_set_translation_domain (actions_edit_one_object
, PACKAGE
);
3833 gtk_action_group_add_actions (actions_edit_one_object
, action_entries_edit_one_object
, G_N_ELEMENTS(action_entries_edit_one_object
), NULL
);
3835 actions_edit_map
=gtk_action_group_new("edit_map");
3836 gtk_action_group_set_translation_domain (actions_edit_map
, PACKAGE
);
3837 gtk_action_group_add_actions (actions_edit_map
, action_entries_edit_map
, G_N_ELEMENTS(action_entries_edit_map
), NULL
);
3839 actions_edit_random
=gtk_action_group_new("edit_random");
3840 gtk_action_group_set_translation_domain (actions_edit_random
, PACKAGE
);
3841 gtk_action_group_add_actions (actions_edit_random
, action_entries_edit_random
, G_N_ELEMENTS(action_entries_edit_random
), NULL
);
3843 actions_clipboard
=gtk_action_group_new("clipboard");
3844 gtk_action_group_set_translation_domain (actions_clipboard
, PACKAGE
);
3845 gtk_action_group_add_actions (actions_clipboard
, action_entries_clipboard
, G_N_ELEMENTS(action_entries_clipboard
), NULL
);
3847 actions_clipboard_paste
=gtk_action_group_new("clipboard_paste");
3848 gtk_action_group_set_translation_domain (actions_clipboard_paste
, PACKAGE
);
3849 gtk_action_group_add_actions (actions_clipboard_paste
, action_entries_clipboard_paste
, G_N_ELEMENTS(action_entries_clipboard_paste
), NULL
);
3851 actions_edit_undo
=gtk_action_group_new("edit_undo");
3852 gtk_action_group_set_translation_domain (actions_edit_undo
, PACKAGE
);
3853 gtk_action_group_add_actions (actions_edit_undo
, action_entries_edit_undo
, G_N_ELEMENTS(action_entries_edit_undo
), NULL
);
3855 actions_edit_redo
=gtk_action_group_new("edit_redo");
3856 gtk_action_group_set_translation_domain (actions_edit_redo
, PACKAGE
);
3857 gtk_action_group_add_actions (actions_edit_redo
, action_entries_edit_redo
, G_N_ELEMENTS(action_entries_edit_redo
), NULL
);
3859 actions_edit_cave
=gtk_action_group_new("edit_cave");
3860 gtk_action_group_set_translation_domain (actions_edit_cave
, PACKAGE
);
3861 gtk_action_group_add_actions (actions_edit_cave
, action_entries_edit_cave
, G_N_ELEMENTS(action_entries_edit_cave
), NULL
);
3862 g_object_set (gtk_action_group_get_action (actions_edit_cave
, "Test"), "is_important", TRUE
, NULL
);
3863 g_object_set (gtk_action_group_get_action (actions_edit_cave
, "CaveSelector"), "is_important", TRUE
, NULL
);
3865 actions_edit_caveset
=gtk_action_group_new("edit_caveset");
3866 gtk_action_group_set_translation_domain (actions_edit_caveset
, PACKAGE
);
3867 gtk_action_group_add_actions (actions_edit_caveset
, action_entries_edit_caveset
, G_N_ELEMENTS(action_entries_edit_caveset
), NULL
);
3869 actions_cave_selector
=gtk_action_group_new("cave_selector");
3870 gtk_action_group_set_translation_domain (actions_cave_selector
, PACKAGE
);
3871 gtk_action_group_add_actions (actions_cave_selector
, action_entries_cave_selector
, G_N_ELEMENTS(action_entries_cave_selector
), NULL
);
3873 actions_toggle
=gtk_action_group_new("toggles");
3874 gtk_action_group_set_translation_domain (actions_toggle
, PACKAGE
);
3875 gtk_action_group_add_toggle_actions (actions_toggle
, action_entries_toggle
, G_N_ELEMENTS(action_entries_toggle
), NULL
);
3877 ui
=gtk_ui_manager_new();
3878 gtk_ui_manager_insert_action_group(ui
, actions
, 0);
3879 gtk_ui_manager_insert_action_group(ui
, actions_edit_tools
, 0);
3880 gtk_ui_manager_insert_action_group(ui
, actions_edit_map
, 0);
3881 gtk_ui_manager_insert_action_group(ui
, actions_edit_random
, 0);
3882 gtk_ui_manager_insert_action_group(ui
, actions_edit_object
, 0);
3883 gtk_ui_manager_insert_action_group(ui
, actions_edit_one_object
, 0);
3884 gtk_ui_manager_insert_action_group(ui
, actions_clipboard
, 0);
3885 gtk_ui_manager_insert_action_group(ui
, actions_clipboard_paste
, 0);
3886 gtk_ui_manager_insert_action_group(ui
, actions_edit_cave
, 0);
3887 gtk_ui_manager_insert_action_group(ui
, actions_edit_caveset
, 0);
3888 gtk_ui_manager_insert_action_group(ui
, actions_edit_undo
, 0);
3889 gtk_ui_manager_insert_action_group(ui
, actions_edit_redo
, 0);
3890 gtk_ui_manager_insert_action_group(ui
, actions_cave_selector
, 0);
3891 gtk_ui_manager_insert_action_group(ui
, actions_toggle
, 0);
3892 gtk_ui_manager_add_ui_from_string (ui
, ui_info
, -1, NULL
);
3893 gtk_box_pack_start(GTK_BOX(vbox
), gtk_ui_manager_get_widget (ui
, "/MenuBar"), FALSE
, FALSE
, 0);
3895 /* make a submenu, which contains the engine defaults compiled in. */
3896 menu
=gtk_menu_new();
3897 gtk_menu_item_set_submenu(GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui
, "/MenuBar/EditMenu/CaveMenu/EngineDefaults")), menu
);
3898 for (int i
=0; i
<GD_ENGINE_MAX
; i
++) {
3899 GtkWidget
*menuitem
=gtk_menu_item_new_with_label(visible_name(GdEngineEnum(i
)));
3901 gtk_menu_shell_append(GTK_MENU_SHELL (menu
), menuitem
);
3902 gtk_widget_show (menuitem
);
3903 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(set_engine_default_cb
), GINT_TO_POINTER (i
));
3907 toolbars
=gtk_vbox_new(FALSE
, 0);
3908 gtk_box_pack_start(GTK_BOX(vbox
), toolbars
, FALSE
, FALSE
, 0);
3909 gtk_box_pack_start(GTK_BOX(toolbars
), gtk_ui_manager_get_widget (ui
, "/ToolBar"), FALSE
, FALSE
, 0);
3910 gtk_toolbar_set_tooltips(GTK_TOOLBAR (gtk_ui_manager_get_widget (ui
, "/ToolBar")), TRUE
);
3911 gtk_toolbar_set_style(GTK_TOOLBAR(gtk_ui_manager_get_widget (ui
, "/ToolBar")), GTK_TOOLBAR_BOTH_HORIZ
);
3912 gtk_window_add_accel_group(GTK_WINDOW(gd_editor_window
), gtk_ui_manager_get_accel_group(ui
));
3914 /* get popups and attach them to the window, so they are not destroyed (the window holds the ref) */
3915 drawing_area_popup
=gtk_ui_manager_get_widget (ui
, "/DrawingAreaPopup");
3916 gtk_menu_attach_to_widget(GTK_MENU(drawing_area_popup
), gd_editor_window
, NULL
);
3917 object_list_popup
=gtk_ui_manager_get_widget (ui
, "/ObjectListPopup");
3918 gtk_menu_attach_to_widget(GTK_MENU(object_list_popup
), gd_editor_window
, NULL
);
3919 caveset_popup
=gtk_ui_manager_get_widget (ui
, "/CavesetPopup");
3920 gtk_menu_attach_to_widget(GTK_MENU(caveset_popup
), gd_editor_window
, NULL
);
3922 g_object_unref(actions
);
3925 /* combo boxes under toolbar */
3926 hbox_combo
=gtk_hbox_new(FALSE
, 6);
3927 gtk_box_pack_start(GTK_BOX(toolbars
), hbox_combo
, FALSE
, FALSE
, 0);
3929 /* currently shown level - gtkscale */
3930 level_scale
=gtk_hscale_new_with_range (1.0, 5.0, 1.0);
3931 gtk_scale_set_digits (GTK_SCALE(level_scale
), 0);
3932 gtk_scale_set_value_pos (GTK_SCALE(level_scale
), GTK_POS_LEFT
);
3933 g_signal_connect(G_OBJECT(level_scale
), "value-changed", G_CALLBACK(level_scale_changed_cb
), NULL
);
3934 gtk_box_pack_end_defaults(GTK_BOX(hbox_combo
), level_scale
);
3935 gtk_box_pack_end(GTK_BOX(hbox_combo
), gtk_label_new(_("Level shown:")), FALSE
, FALSE
, 0);
3937 /* "new object will be placed on" - combo */
3938 new_object_level_combo
=gtk_combo_box_new_text();
3939 for (unsigned i
=0; i
<G_N_ELEMENTS(new_objects_visible_on
); ++i
)
3940 gtk_combo_box_append_text(GTK_COMBO_BOX(new_object_level_combo
), _(new_objects_visible_on
[i
].text
));
3941 g_signal_connect(G_OBJECT(new_object_level_combo
), "changed", G_CALLBACK(new_object_combo_changed_cb
), NULL
);
3942 gtk_combo_box_set_active(GTK_COMBO_BOX(new_object_level_combo
), 0);
3943 gtk_box_pack_end_defaults(GTK_BOX(hbox_combo
), new_object_level_combo
);
3944 gtk_box_pack_end(GTK_BOX(hbox_combo
), gtk_label_new(_("Draw on:")), FALSE
, FALSE
, 0);
3947 gtk_box_pack_start(GTK_BOX(hbox_combo
), label_first_element
=gtk_label_new(NULL
), FALSE
, FALSE
, 0);
3948 element_button
=gd_element_button_new(O_DIRT
, TRUE
, NULL
); /* combo box of object, default element dirt (not really important what it is) */
3949 gtk_widget_set_tooltip_text(element_button
, _("Element used to draw points, lines, and rectangles. You can use middle-click to pick one from the cave."));
3950 gtk_box_pack_start_defaults(GTK_BOX(hbox_combo
), element_button
);
3952 gtk_box_pack_start(GTK_BOX(hbox_combo
), label_second_element
=gtk_label_new(NULL
), FALSE
, FALSE
, 0);
3953 fillelement_button
=gd_element_button_new(O_SPACE
, TRUE
, NULL
); /* combo box, default element space (not really important what it is) */
3954 gtk_widget_set_tooltip_text (fillelement_button
, _("Element used to fill rectangles, and second element of joins. You can use Ctrl + middle-click to pick one from the cave."));
3955 gtk_box_pack_start_defaults(GTK_BOX(hbox_combo
), fillelement_button
);
3957 /* hbox for drawing area and object list */
3958 hbox
=gtk_hbox_new(FALSE
, 6);
3959 gtk_box_pack_start_defaults(GTK_BOX(vbox
), hbox
);
3961 /* scroll window for drawing area and icon view ****************************************/
3962 scroll_window
=gtk_scrolled_window_new(NULL
, NULL
);
3963 gtk_box_pack_start_defaults(GTK_BOX(hbox
), scroll_window
);
3964 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll_window
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
3966 /* object list ***************************************/
3967 scroll_window_objects
=gtk_scrolled_window_new(NULL
, NULL
);
3968 gtk_box_pack_start(GTK_BOX(hbox
), scroll_window_objects
, FALSE
, FALSE
, 0);
3969 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll_window_objects
), GTK_SHADOW_ETCHED_IN
);
3970 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll_window_objects
), GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
3972 object_list
=gtk_list_store_new(NUM_EDITOR_COLUMNS
, G_TYPE_INT
, G_TYPE_STRING
, G_TYPE_STRING
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, G_TYPE_POINTER
);
3973 object_list_tree_view
=gtk_tree_view_new_with_model(GTK_TREE_MODEL (object_list
));
3974 g_object_unref(object_list
);
3975 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW (object_list_tree_view
))), "changed", G_CALLBACK(object_list_selection_changed_signal
), NULL
);
3976 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(object_list_tree_view
)), GTK_SELECTION_MULTIPLE
);
3977 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(object_list_tree_view
), TRUE
);
3978 gtk_container_add(GTK_CONTAINER(scroll_window_objects
), object_list_tree_view
);
3979 /* two signals which are required to handle cave object drag-and-drop reordering */
3980 g_signal_connect(G_OBJECT(object_list
), "row-changed", G_CALLBACK(object_list_row_changed
), NULL
);
3981 g_signal_connect(G_OBJECT(object_list
), "row-deleted", G_CALLBACK(object_list_row_delete
), NULL
);
3982 /* object double-click: */
3983 g_signal_connect(G_OBJECT(object_list_tree_view
), "row-activated", G_CALLBACK(object_list_row_activated
), NULL
);
3984 g_signal_connect(G_OBJECT(object_list_tree_view
), "popup-menu", G_CALLBACK(object_list_show_popup_menu
), NULL
);
3985 g_signal_connect(G_OBJECT(object_list_tree_view
), "button-press-event", G_CALLBACK(object_list_button_press_event
), NULL
);
3987 /* tree view column which holds all data */
3988 /* we do not allow sorting, as it disables drag and drop */
3989 column
=gtk_tree_view_column_new();
3990 gtk_tree_view_column_set_spacing (column
, 1);
3991 gtk_tree_view_column_set_title (column
, _("_Objects"));
3992 renderer
=gtk_cell_renderer_pixbuf_new();
3993 gtk_tree_view_column_pack_start (column
, renderer
, FALSE
);
3994 gtk_tree_view_column_set_attributes (column
, renderer
, "stock-id", LEVELS_PIXBUF_COLUMN
, NULL
);
3995 renderer
=gtk_cell_renderer_pixbuf_new();
3996 gtk_tree_view_column_pack_start (column
, renderer
, FALSE
);
3997 gtk_tree_view_column_set_attributes (column
, renderer
, "stock-id", TYPE_PIXBUF_COLUMN
, NULL
);
3998 renderer
=gtk_cell_renderer_pixbuf_new();
3999 gtk_tree_view_column_pack_start (column
, renderer
, FALSE
);
4000 gtk_tree_view_column_set_attributes (column
, renderer
, "pixbuf", ELEMENT_PIXBUF_COLUMN
, NULL
);
4001 renderer
=gtk_cell_renderer_text_new();
4002 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
4003 gtk_tree_view_column_set_attributes (column
, renderer
, "text", TEXT_COLUMN
, NULL
);
4004 gtk_tree_view_append_column (GTK_TREE_VIEW (object_list_tree_view
), column
);
4006 /* something like a statusbar, maybe that would be nicer */
4007 hbox
=gtk_hbox_new(FALSE
, 6);
4008 gtk_box_pack_end(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
4009 label_coordinate
=gtk_label_new("[x: y: ]");
4010 gtk_box_pack_start(GTK_BOX(hbox
), label_coordinate
, FALSE
, FALSE
, 0);
4011 label_object
=gtk_label_new(NULL
);
4012 gtk_box_pack_start(GTK_BOX(hbox
), label_object
, FALSE
, FALSE
, 0);
4014 edit_level
=0; /* view: level 1 */
4015 /* here we force selection and update, by calling the function twice with different args */
4016 select_tool(TOOL_LINE
);
4017 select_tool(TOOL_MOVE
);
4018 timeout_id
= g_timeout_add(40, drawing_area_draw_timeout
, NULL
);
4020 gtk_widget_show_all(gd_editor_window
);
4021 gtk_window_present(GTK_WINDOW(gd_editor_window
));
4022 /* to remember size; only attach signal after showing */
4023 g_signal_connect(G_OBJECT(gd_editor_window
), "configure-event", G_CALLBACK(editor_window_configure_event
), NULL
);
4025 select_cave_for_edit(NULL
);
4029 void gd_cave_editor_run(CaveSet
*caveset
) {
4030 create_cave_editor(caveset
);
4032 gtk_widget_destroy(gd_editor_window
);
4033 /* process all pending events before returning,
4034 * because the widget destroying created many. without processing them,
4035 * the editor window would not disappear! */
4036 while (gtk_events_pending())
4037 gtk_main_iteration();