1 /***********************************************************************
2 Freeciv - Copyright (C) 2005 - The Freeciv Project
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 #include <limits.h> /* USHRT_MAX */
19
20 #include <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
22
23 /* utility */
24 #include "bitvector.h"
25 #include "fc_cmdline.h"
26 #include "fcintl.h"
27 #include "log.h"
28 #include "mem.h"
29
30 /* common */
31 #include "fc_interface.h"
32 #include "game.h"
33 #include "government.h"
34 #include "map.h"
35 #include "movement.h"
36 #include "research.h"
37 #include "tile.h"
38
39 /* client */
40 #include "client_main.h"
41 #include "climisc.h"
42 #include "editor.h"
43 #include "mapview_common.h"
44 #include "tilespec.h"
45
46 /* client/gui-gtk-3.0 */
47 #include "canvas.h"
48 #include "gui_main.h"
49 #include "gui_stuff.h"
50 #include "plrdlg.h"
51 #include "sprite.h"
52
53 #include "editprop.h"
54
55
56 /* Forward declarations. */
57 struct objprop;
58 struct objbind;
59 struct property_page;
60 struct property_editor;
61 struct extviewer;
62
63 /****************************************************************************
64 Miscellaneous helpers.
65 ****************************************************************************/
66 static GdkPixbuf *create_pixbuf_from_layers(const struct tile *ptile,
67 const struct unit *punit,
68 const struct city *pcity,
69 int *layers,
70 int num_layers);
71 static GdkPixbuf *create_tile_pixbuf(const struct tile *ptile);
72 static GdkPixbuf *create_unit_pixbuf(const struct unit *punit);
73 static GdkPixbuf *create_city_pixbuf(const struct city *pcity);
74
75 static void add_column(GtkWidget *view,
76 int col_id,
77 const char *name,
78 GType gtype,
79 bool editable,
80 bool is_radio,
81 GCallback edit_callback,
82 gpointer callback_userdata);
83
84 static bool can_create_unit_at_tile(struct tile *ptile);
85
86 static int get_next_unique_tag(void);
87
88 /* 'struct stored_tag_hash' and related functions. */
89 #define SPECHASH_TAG stored_tag
90 #define SPECHASH_INT_KEY_TYPE
91 #define SPECHASH_INT_DATA_TYPE
92 #include "spechash.h"
93
94 /* NB: If packet definitions change, be sure to
95 * update objbind_pack_current_values!!! */
96 union packetdata {
97 struct {
98 gpointer v_pointer1;
99 gpointer v_pointer2;
100 } pointers;
101 struct packet_edit_tile *tile;
102 struct packet_edit_startpos_full *startpos;
103 struct packet_edit_city *city;
104 struct packet_edit_unit *unit;
105 struct packet_edit_player *player;
106 struct {
107 struct packet_edit_game *game;
108 struct packet_edit_scenario_desc *desc;
109 } game;
110 };
111
112 /* Helpers for the OPID_TILE_VISION property. */
113 struct tile_vision_data {
114 bv_player tile_known, tile_seen[V_COUNT];
115 };
116 const char *vision_layer_get_name(enum vision_layer);
117
118 #define PF_MAX_CLAUSES 16
119 #define PF_DISJUNCTION_SEPARATOR "|"
120 #define PF_CONJUNCTION_SEPARATOR "&"
121
122 struct pf_pattern {
123 bool negate;
124 char *text;
125 };
126
127 struct pf_conjunction {
128 struct pf_pattern conjunction[PF_MAX_CLAUSES];
129 int count;
130 };
131
132 struct property_filter {
133 struct pf_conjunction disjunction[PF_MAX_CLAUSES];
134 int count;
135 };
136
137 static struct property_filter *property_filter_new(const char *filter);
138 static bool property_filter_match(struct property_filter *pf,
139 const struct objprop *op);
140 static void property_filter_free(struct property_filter *pf);
141
142
143 /****************************************************************************
144 Object type declarations.
145
146 To add a new object type:
147 1. Add a value in enum editor_object_type in client/editor.h.
148 2. Add a string name to objtype_get_name.
149 3. Add code in objtype_get_id_from_object.
150 4. Add code in objtype_get_object_from_id.
151 5. Add a case handler in objtype_is_conserved, and if
152 the object type is not conserved, then also in
153 objbind_request_destroy_object and property_page_create_objects.
154 6. Add an if-block in objbind_get_value_from_object.
155 7. Add an if-block in objbind_get_allowed_value_span.
156 9. Add a case handler in property_page_setup_objprops.
157 10. Add a case handler in property_page_add_objbinds_from_tile
158 if applicable.
159
160 Furthermore, if the object type is to be editable:
161 11. Define its edit packet in common/packets.def.
162 12. Add the packet handler in server/edithand.c.
163 13. Add its edit packet type to union packetdata.
164 14. Add an if-block in objbind_pack_current_values.
165 15. Add an if-block in objbind_pack_modified_value.
166 16. Add code in property_page_new_packet.
167 17. Add code in property_page_send_packet.
168 18. Add calls to editgui_notify_object_changed in
169 client/packhand.c or where applicable.
170
171 ****************************************************************************/
172
173 /* OBJTYPE_* enum values defined in client/editor.h */
174
175 static const char *objtype_get_name(enum editor_object_type objtype);
176 static int objtype_get_id_from_object(enum editor_object_type objtype,
177 gpointer object);
178 static gpointer objtype_get_object_from_id(enum editor_object_type objtype,
179 int id);
180 static bool objtype_is_conserved(enum editor_object_type objtype);
181
182
183 /****************************************************************************
184 Value type declarations.
185
186 To add a new value type:
187 1. Add a value in enum value_types.
188 2. Add its field in union propval_data.
189 3. Add a case handler in valtype_get_name.
190 4. Add a case handler in propval_copy if needed.
191 5. Add a case handler in propval_free if needed.
192 6. Add a case handler in propval_equal if needed.
193 7. Add a case handler in objprop_get_gtype.
194 8. Add a case handler in property_page_set_store_value.
195 9. Add a case handler in propval_as_string if needed.
196 ****************************************************************************/
197 enum value_types {
198 VALTYPE_NONE = 0,
199 VALTYPE_INT,
200 VALTYPE_BOOL,
201 VALTYPE_STRING,
202 VALTYPE_PIXBUF,
203 VALTYPE_BUILT_ARRAY, /* struct built_status[B_LAST] */
204 VALTYPE_INVENTIONS_ARRAY, /* bool[A_LAST] */
205 VALTYPE_BV_SPECIAL,
206 VALTYPE_BV_ROADS,
207 VALTYPE_BV_BASES,
208 VALTYPE_NATION,
209 VALTYPE_NATION_HASH, /* struct nation_hash */
210 VALTYPE_GOV,
211 VALTYPE_TILE_VISION_DATA /* struct tile_vision_data */
212 };
213
214 static const char *valtype_get_name(enum value_types valtype);
215
216
217 /****************************************************************************
218 Propstate and propval declarations.
219
220 To add a new member to union propval_data, see the steps for adding a
221 new value type above.
222
223 New property values are "constructed" by objbind_get_value_from_object.
224 ****************************************************************************/
225 union propval_data {
226 gpointer v_pointer;
227 int v_int;
228 bool v_bool;
229 char *v_string;
230 const char *v_const_string;
231 GdkPixbuf *v_pixbuf;
232 struct built_status *v_built;
233 bv_special v_bv_special;
234 bv_roads v_bv_roads;
235 bv_bases v_bv_bases;
236 struct nation_type *v_nation;
237 struct nation_hash *v_nation_hash;
238 struct government *v_gov;
239 bv_techs v_bv_inventions;
240 struct tile_vision_data *v_tile_vision;
241 };
242
243 struct propval {
244 union propval_data data;
245 enum value_types valtype;
246 bool must_free;
247 };
248
249 static void propval_free(struct propval *pv);
250 static void propval_free_data(struct propval *pv);
251 static struct propval *propval_copy(struct propval *pv);
252 static bool propval_equal(struct propval *pva, struct propval *pvb);
253
254 struct propstate {
255 int property_id;
256 struct propval *property_value;
257 };
258
259 static struct propstate *propstate_new(struct objprop *op,
260 struct propval *pv);
261 static void propstate_destroy(struct propstate *ps);
262 static void propstate_clear_value(struct propstate *ps);
263 static void propstate_set_value(struct propstate *ps,
264 struct propval *pv);
265 static struct propval *propstate_get_value(struct propstate *ps);
266
267 #define SPECHASH_TAG propstate
268 #define SPECHASH_INT_KEY_TYPE
269 #define SPECHASH_IDATA_TYPE struct propstate *
270 #define SPECHASH_IDATA_FREE propstate_destroy
271 #include "spechash.h"
272
273
274 /****************************************************************************
275 Objprop declarations.
276
277 To add a new object property:
278 1. Add a value in enum object_property_ids (grouped by
279 object type).
280 2. Define the property in property_page_setup_objprops.
281 3. Add a case handler in objbind_get_value_from_object
282 in the appropriate object type block.
283 4. Add a case handler in objprop_setup_widget.
284 5. Add a case handler in objprop_refresh_widget.
285
286 Furthermore, if the property is editable:
287 5. Add a field for this property in the edit
288 packet for this object type in common/packets.def.
289 6. Add a case handler in objbind_pack_modified_value.
290 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
291 !!! 7. Add code to set the packet field in !!!
292 !!! objbind_pack_current_values. !!!
293 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
294 8. Add code to handle changes in the packet field in
295 server/edithand.c handle_edit_<objtype>.
296
297 If the property makes use of an extviewer:
298 9. Handle widget creation in extviewer_new.
299 10. Handle refresh in extviewer_refresh_widgets.
300 11. Handle clear in extviewer_clear_widgets.
301 12. Handle any signal callbacks (e.g. toggled) if needed.
302
303 TODO: Add more object properties.
304 ****************************************************************************/
305 enum object_property_ids {
306 OPID_TILE_IMAGE,
307 OPID_TILE_X,
308 OPID_TILE_Y,
309 OPID_TILE_NAT_X,
310 OPID_TILE_NAT_Y,
311 OPID_TILE_CONTINENT,
312 #ifdef DEBUG
313 OPID_TILE_ADDRESS,
314 #endif /* DEBUG */
315 OPID_TILE_TERRAIN,
316 OPID_TILE_INDEX,
317 OPID_TILE_XY,
318 OPID_TILE_RESOURCE,
319 OPID_TILE_SPECIALS,
320 OPID_TILE_ROADS,
321 OPID_TILE_BASES,
322 OPID_TILE_VISION, /* tile_known and tile_seen */
323 OPID_TILE_LABEL,
324
325 OPID_STARTPOS_IMAGE,
326 OPID_STARTPOS_XY,
327 OPID_STARTPOS_EXCLUDE,
328 OPID_STARTPOS_NATIONS,
329
330 OPID_UNIT_IMAGE,
331 #ifdef DEBUG
332 OPID_UNIT_ADDRESS,
333 #endif /* DEBUG */
334 OPID_UNIT_TYPE,
335 OPID_UNIT_ID,
336 OPID_UNIT_XY,
337 OPID_UNIT_MOVES_LEFT,
338 OPID_UNIT_FUEL,
339 OPID_UNIT_MOVED,
340 OPID_UNIT_DONE_MOVING,
341 OPID_UNIT_HP,
342 OPID_UNIT_VETERAN,
343
344 OPID_CITY_IMAGE,
345 OPID_CITY_NAME,
346 #ifdef DEBUG
347 OPID_CITY_ADDRESS,
348 #endif /* DEBUG */
349 OPID_CITY_ID,
350 OPID_CITY_XY,
351 OPID_CITY_SIZE,
352 OPID_CITY_HISTORY,
353 OPID_CITY_BUILDINGS,
354 OPID_CITY_FOOD_STOCK,
355 OPID_CITY_SHIELD_STOCK,
356
357 OPID_PLAYER_NAME,
358 OPID_PLAYER_NATION,
359 OPID_PLAYER_GOV,
360 OPID_PLAYER_AGE,
361 #ifdef FREECIV_DEBUG
362 OPID_PLAYER_ADDRESS,
363 #endif /* FREECIV_DEBUG */
364 OPID_PLAYER_INVENTIONS,
365 OPID_PLAYER_SCIENCE,
366 OPID_PLAYER_GOLD,
367
368 OPID_GAME_YEAR,
369 OPID_GAME_SCENARIO,
370 OPID_GAME_SCENARIO_NAME,
371 OPID_GAME_SCENARIO_AUTHORS,
372 OPID_GAME_SCENARIO_DESC,
373 OPID_GAME_SCENARIO_RANDSTATE,
374 OPID_GAME_SCENARIO_PLAYERS,
375 OPID_GAME_STARTPOS_NATIONS,
376 OPID_GAME_PREVENT_CITIES,
377 OPID_GAME_LAKE_FLOODING
378 };
379
380 enum object_property_flags {
381 OPF_NO_FLAGS = 0,
382 OPF_EDITABLE = 1 << 0,
383 OPF_IN_LISTVIEW = 1 << 1,
384 OPF_HAS_WIDGET = 1 << 2
385 };
386
387 struct objprop {
388 int id;
389 const char *name;
390 const char *tooltip;
391 enum object_property_flags flags;
392 enum value_types valtype;
393 int column_id;
394 GtkTreeViewColumn *view_column;
395 GtkWidget *widget;
396 struct extviewer *extviewer;
397 struct property_page *parent_page;
398 };
399
400 static struct objprop *objprop_new(int id,
401 const char *name,
402 const char *tooltip,
403 enum object_property_flags flags,
404 enum value_types valtype,
405 struct property_page *parent);
406 static int objprop_get_id(const struct objprop *op);
407 static const char *objprop_get_name(const struct objprop *op);
408 static const char *objprop_get_tooltip(const struct objprop *op);
409 static enum value_types objprop_get_valtype(const struct objprop *op);
410 static struct property_page *
411 objprop_get_property_page(const struct objprop *op);
412
413 static bool objprop_show_in_listview(const struct objprop *op);
414 static bool objprop_is_sortable(const struct objprop *op);
415 static bool objprop_is_readonly(const struct objprop *op);
416 static bool objprop_has_widget(const struct objprop *op);
417
418 static GType objprop_get_gtype(const struct objprop *op);
419 static const char *objprop_get_attribute_type_string(const struct objprop *op);
420 static void objprop_set_column_id(struct objprop *op, int col_id);
421 static int objprop_get_column_id(const struct objprop *op);
422 static void objprop_set_treeview_column(struct objprop *op,
423 GtkTreeViewColumn *col);
424 static GtkTreeViewColumn *objprop_get_treeview_column(const struct objprop *op);
425 static GtkCellRenderer *objprop_create_cell_renderer(const struct objprop *op);
426
427 static void objprop_setup_widget(struct objprop *op);
428 static GtkWidget *objprop_get_widget(struct objprop *op);
429 static void objprop_set_child_widget(struct objprop *op,
430 const char *widget_name,
431 GtkWidget *widget);
432 static GtkWidget *objprop_get_child_widget(struct objprop *op,
433 const char *widget_name);
434 static void objprop_set_extviewer(struct objprop *op,
435 struct extviewer *ev);
436 static struct extviewer *objprop_get_extviewer(struct objprop *op);
437 static void objprop_refresh_widget(struct objprop *op,
438 struct objbind *ob);
439 static void objprop_widget_entry_changed(GtkEntry *entry, gpointer userdata);
440 static void objprop_widget_spin_button_changed(GtkSpinButton *spin,
441 gpointer userdata);
442 static void objprop_widget_toggle_button_changed(GtkToggleButton *button,
443 gpointer userdata);
444
445 #define SPECHASH_TAG objprop
446 #define SPECHASH_INT_KEY_TYPE
447 #define SPECHASH_IDATA_TYPE struct objprop *
448 #include "spechash.h"
449
450
451 /****************************************************************************
452 Objbind declarations.
453 ****************************************************************************/
454 struct objbind {
455 enum editor_object_type objtype;
456 int object_id;
457 struct property_page *parent_property_page;
458 struct propstate_hash *propstate_table;
459 GtkTreeRowReference *rowref;
460 };
461
462 static struct objbind *objbind_new(enum editor_object_type objtype,
463 gpointer object);
464 static void objbind_destroy(struct objbind *ob);
465 static enum editor_object_type objbind_get_objtype(const struct objbind *ob);
466 static void objbind_bind_properties(struct objbind *ob,
467 struct property_page *pp);
468 static gpointer objbind_get_object(struct objbind *ob);
469 static int objbind_get_object_id(struct objbind *ob);
470 static void objbind_request_destroy_object(struct objbind *ob);
471 static struct propval *objbind_get_value_from_object(struct objbind *ob,
472 struct objprop *op);
473 static bool objbind_get_allowed_value_span(struct objbind *ob,
474 struct objprop *op,
475 double *pmin,
476 double *pmax,
477 double *pstep,
478 double *pbig_step);
479 static void objbind_set_modified_value(struct objbind *ob,
480 struct objprop *op,
481 struct propval *pv);
482 static struct propval *objbind_get_modified_value(struct objbind *ob,
483 struct objprop *op);
484 static void objbind_clear_modified_value(struct objbind *ob,
485 struct objprop *op);
486 static bool objbind_property_is_modified(struct objbind *ob,
487 struct objprop *op);
488 static bool objbind_has_modified_properties(struct objbind *ob);
489 static void objbind_clear_all_modified_values(struct objbind *ob);
490 static void objbind_pack_current_values(struct objbind *ob,
491 union packetdata packet);
492 static void objbind_pack_modified_value(struct objbind *ob,
493 struct objprop *op,
494 union packetdata packet);
495 static void objbind_set_rowref(struct objbind *ob,
496 GtkTreeRowReference *rr);
497 static GtkTreeRowReference *objbind_get_rowref(struct objbind *ob);
498
499 #define SPECHASH_TAG objbind
500 #define SPECHASH_INT_KEY_TYPE
501 #define SPECHASH_IDATA_TYPE struct objbind *
502 #define SPECHASH_IDATA_FREE objbind_destroy
503 #include "spechash.h"
504
505
506 /****************************************************************************
507 Extended property viewer declarations. This is a set of widgets used
508 for viewing and/or editing properties with complex values (e.g. arrays).
509 ****************************************************************************/
510 struct extviewer {
511 struct objprop *objprop;
512 struct propval *pv_cached;
513
514 GtkWidget *panel_widget;
515 GtkWidget *panel_label;
516 GtkWidget *panel_button;
517 GtkWidget *panel_image;
518
519 GtkWidget *view_widget;
520 GtkWidget *view_label;
521
522 GtkListStore *store;
523 GtkTextBuffer *textbuf;
524 };
525
526 static struct extviewer *extviewer_new(struct objprop *op);
527 static struct objprop *extviewer_get_objprop(struct extviewer *ev);
528 static GtkWidget *extviewer_get_panel_widget(struct extviewer *ev);
529 static GtkWidget *extviewer_get_view_widget(struct extviewer *ev);
530 static void extviewer_refresh_widgets(struct extviewer *ev,
531 struct propval *pv);
532 static void extviewer_clear_widgets(struct extviewer *ev);
533 static void extviewer_panel_button_clicked(GtkButton *button,
534 gpointer userdata);
535 static void extviewer_view_cell_toggled(GtkCellRendererToggle *cell,
536 gchar *path,
537 gpointer userdata);
538 static void extviewer_textbuf_changed(GtkTextBuffer *textbuf,
539 gpointer userdata);
540
541
542 /****************************************************************************
543 Property page declarations.
544 ****************************************************************************/
545 struct property_page {
546 enum editor_object_type objtype;
547
548 GtkWidget *widget;
549 GtkListStore *object_store;
550 GtkWidget *object_view;
551 GtkWidget *extviewer_notebook;
552
553 struct property_editor *pe_parent;
554
555 struct objprop_hash *objprop_table;
556 struct objbind_hash *objbind_table;
557 struct stored_tag_hash *tag_table;
558
559 struct objbind *focused_objbind;
560 };
561
562 static struct property_page *
563 property_page_new(enum editor_object_type objtype,
564 struct property_editor *parent);
565 static void property_page_setup_objprops(struct property_page *pp);
566 static const char *property_page_get_name(const struct property_page *pp);
567 static enum editor_object_type
568 property_page_get_objtype(const struct property_page *pp);
569 static void property_page_load_tiles(struct property_page *pp,
570 const struct tile_list *tiles);
571 static void property_page_add_objbinds_from_tile(struct property_page *pp,
572 const struct tile *ptile);
573 static int property_page_get_num_objbinds(const struct property_page *pp);
574 static void property_page_clear_objbinds(struct property_page *pp);
575 static void property_page_add_objbind(struct property_page *pp,
576 gpointer object_data);
577 static void property_page_fill_widgets(struct property_page *pp);
578 static struct objbind *
579 property_page_get_focused_objbind(struct property_page *pp);
580 static void property_page_set_focused_objbind(struct property_page *pp,
581 struct objbind *ob);
582 static struct objbind *property_page_get_objbind(struct property_page *pp,
583 int object_id);
584 static void property_page_selection_changed(GtkTreeSelection *sel,
585 gpointer userdata);
586 static gboolean property_page_selection_func(GtkTreeSelection *sel,
587 GtkTreeModel *model,
588 GtkTreePath *path,
589 gboolean currently_selected,
590 gpointer data);
591 static void property_page_quick_find_entry_changed(GtkWidget *entry,
592 gpointer userdata);
593 static void property_page_change_value(struct property_page *pp,
594 struct objprop *op,
595 struct propval *pv);
596 static void property_page_send_values(struct property_page *pp);
597 static void property_page_reset_objbinds(struct property_page *pp);
598 static void property_page_destroy_objects(struct property_page *pp);
599 static void property_page_create_objects(struct property_page *pp,
600 struct tile_list *hint_tiles);
601 static union packetdata property_page_new_packet(struct property_page *pp);
602 static void property_page_send_packet(struct property_page *pp,
603 union packetdata packet);
604 static void property_page_free_packet(struct property_page *pp,
605 union packetdata packet);
606 static void property_page_object_changed(struct property_page *pp,
607 int object_id,
608 bool remove);
609 static void property_page_object_created(struct property_page *pp,
610 int tag, int object_id);
611 static void property_page_add_extviewer(struct property_page *pp,
612 struct extviewer *ev);
613 static void property_page_show_extviewer(struct property_page *pp,
614 struct extviewer *ev);
615 static void property_page_store_creation_tag(struct property_page *pp,
616 int tag, int count);
617 static void property_page_remove_creation_tag(struct property_page *pp,
618 int tag);
619 static bool property_page_tag_is_known(struct property_page *pp, int tag);
620 static void property_page_clear_tags(struct property_page *pp);
621 static void property_page_apply_button_clicked(GtkButton *button,
622 gpointer userdata);
623 static void property_page_refresh_button_clicked(GtkButton *button,
624 gpointer userdata);
625 static void property_page_create_button_clicked(GtkButton *button,
626 gpointer userdata);
627 static void property_page_destroy_button_clicked(GtkButton *button,
628 gpointer userdata);
629
630
631 #define property_page_objprop_iterate(ARG_pp, NAME_op) \
632 TYPED_HASH_DATA_ITERATE(struct objprop *, (ARG_pp)->objprop_table, NAME_op)
633 #define property_page_objprop_iterate_end HASH_DATA_ITERATE_END
634
635 #define property_page_objbind_iterate(ARG_pp, NAME_ob) \
636 TYPED_HASH_DATA_ITERATE(struct objbind *, (ARG_pp)->objbind_table, NAME_ob)
637 #define property_page_objbind_iterate_end HASH_DATA_ITERATE_END
638
639
640 /****************************************************************************
641 Property editor declarations.
642 ****************************************************************************/
643 struct property_editor {
644 GtkWidget *widget;
645 GtkWidget *notebook;
646
647 struct property_page *property_pages[NUM_OBJTYPES];
648 };
649
650 static struct property_editor *property_editor_new(void);
651 static bool property_editor_add_page(struct property_editor *pe,
652 enum editor_object_type objtype);
653 static struct property_page *
654 property_editor_get_page(struct property_editor *pe,
655 enum editor_object_type objtype);
656
657 static struct property_editor *the_property_editor;
658
659
660 /****************************************************************************
661 Returns the translated name for the given object type.
662 ****************************************************************************/
objtype_get_name(enum editor_object_type objtype)663 static const char *objtype_get_name(enum editor_object_type objtype)
664 {
665 switch (objtype) {
666 case OBJTYPE_TILE:
667 return _("Tile");
668 case OBJTYPE_STARTPOS:
669 return _("Start Position");
670 case OBJTYPE_UNIT:
671 return _("Unit");
672 case OBJTYPE_CITY:
673 return _("City");
674 case OBJTYPE_PLAYER:
675 return _("Player");
676 case OBJTYPE_GAME:
677 return Q_("?play:Game");
678 case NUM_OBJTYPES:
679 break;
680 }
681
682 log_error("%s() Unhandled request to get name of object type %d.",
683 __FUNCTION__, objtype);
684 return "Unknown";
685 }
686
687 /****************************************************************************
688 Returns the unique identifier value from the given object, assuming it
689 is of the 'objtype' object type. Valid IDs are always greater than or
690 equal to zero.
691 ****************************************************************************/
objtype_get_id_from_object(enum editor_object_type objtype,gpointer object)692 static int objtype_get_id_from_object(enum editor_object_type objtype,
693 gpointer object)
694 {
695 switch (objtype) {
696 case OBJTYPE_TILE:
697 return tile_index((struct tile *) object);
698 case OBJTYPE_STARTPOS:
699 return startpos_number((struct startpos *) object);
700 case OBJTYPE_UNIT:
701 return ((struct unit *) object)->id;
702 case OBJTYPE_CITY:
703 return ((struct city *) object)->id;
704 case OBJTYPE_PLAYER:
705 return player_number((struct player *) object);
706 case OBJTYPE_GAME:
707 return 1;
708 case NUM_OBJTYPES:
709 break;
710 }
711
712 log_error("%s(): Unhandled request to get object ID from object %p of "
713 "type %d (%s).", __FUNCTION__, object, objtype,
714 objtype_get_name(objtype));
715 return -1;
716 }
717
718 /****************************************************************************
719 Get the object of type 'objtype' uniquely identified by 'id'.
720 ****************************************************************************/
objtype_get_object_from_id(enum editor_object_type objtype,int id)721 static gpointer objtype_get_object_from_id(enum editor_object_type objtype,
722 int id)
723 {
724 switch (objtype) {
725 case OBJTYPE_TILE:
726 return index_to_tile(id);
727 case OBJTYPE_STARTPOS:
728 return map_startpos_by_number(id);
729 case OBJTYPE_UNIT:
730 return game_unit_by_number(id);
731 case OBJTYPE_CITY:
732 return game_city_by_number(id);
733 case OBJTYPE_PLAYER:
734 return player_by_number(id);
735 case OBJTYPE_GAME:
736 return &game;
737 case NUM_OBJTYPES:
738 break;
739 }
740
741 log_error("%s(): Unhandled request to get object of type %d (%s) "
742 "with ID %d.", __FUNCTION__, objtype,
743 objtype_get_name(objtype), id);
744 return NULL;
745 }
746
747 /****************************************************************************
748 Returns TRUE if it does not make sense for the object of the given type to
749 be created and destroyed (e.g. tiles, game), as opposed to those that can
750 be (e.g. units, cities, players, etc.).
751 ****************************************************************************/
objtype_is_conserved(enum editor_object_type objtype)752 static bool objtype_is_conserved(enum editor_object_type objtype)
753 {
754 switch (objtype) {
755 case OBJTYPE_TILE:
756 case OBJTYPE_GAME:
757 return TRUE;
758 case OBJTYPE_STARTPOS:
759 case OBJTYPE_UNIT:
760 case OBJTYPE_CITY:
761 case OBJTYPE_PLAYER:
762 return FALSE;
763 case NUM_OBJTYPES:
764 break;
765 }
766
767 log_error("%s(): Unhandled request for object type %d (%s)).",
768 __FUNCTION__, objtype, objtype_get_name(objtype));
769 return TRUE;
770 }
771
772 /****************************************************************************
773 Returns the untranslated name for the given value type.
774 ****************************************************************************/
valtype_get_name(enum value_types valtype)775 static const char *valtype_get_name(enum value_types valtype)
776 {
777 switch (valtype) {
778 case VALTYPE_NONE:
779 return "none";
780 case VALTYPE_STRING:
781 return "string";
782 case VALTYPE_INT:
783 return "int";
784 case VALTYPE_BOOL:
785 return "bool";
786 case VALTYPE_PIXBUF:
787 return "pixbuf";
788 case VALTYPE_BUILT_ARRAY:
789 return "struct built_status[B_LAST]";
790 case VALTYPE_INVENTIONS_ARRAY:
791 return "bool[A_LAST]";
792 case VALTYPE_BV_SPECIAL:
793 return "bv_special";
794 case VALTYPE_BV_ROADS:
795 return "bv_roads";
796 case VALTYPE_BV_BASES:
797 return "bv_bases";
798 case VALTYPE_NATION:
799 return "nation";
800 case VALTYPE_NATION_HASH:
801 return "struct nation_hash";
802 case VALTYPE_GOV:
803 return "government";
804 case VALTYPE_TILE_VISION_DATA:
805 return "struct tile_vision_data";
806 }
807
808 log_error("%s(): unhandled value type %d.", __FUNCTION__, valtype);
809 return "void";
810 }
811
812 /****************************************************************************
813 Convenience function to add a column to a GtkTreeView. Used for the
814 view widget creation in extviewer_new().
815 ****************************************************************************/
add_column(GtkWidget * view,int col_id,const char * name,GType gtype,bool editable,bool is_radio,GCallback edit_callback,gpointer userdata)816 static void add_column(GtkWidget *view,
817 int col_id,
818 const char *name,
819 GType gtype,
820 bool editable,
821 bool is_radio,
822 GCallback edit_callback,
823 gpointer userdata)
824 {
825 GtkCellRenderer *cell;
826 GtkTreeViewColumn *col;
827 const char *attr = NULL;
828
829 if (gtype == G_TYPE_BOOLEAN) {
830 cell = gtk_cell_renderer_toggle_new();
831 gtk_cell_renderer_toggle_set_radio(GTK_CELL_RENDERER_TOGGLE(cell),
832 is_radio);
833 if (editable) {
834 g_signal_connect(cell, "toggled", edit_callback, userdata);
835 }
836 attr = "active";
837 } else if (gtype == GDK_TYPE_PIXBUF) {
838 cell = gtk_cell_renderer_pixbuf_new();
839 attr = "pixbuf";
840 } else {
841 cell = gtk_cell_renderer_text_new();
842 if (editable) {
843 g_object_set(cell, "editable", TRUE, NULL);
844 g_signal_connect(cell, "edited", edit_callback, userdata);
845 }
846 attr = "text";
847 }
848
849 col = gtk_tree_view_column_new_with_attributes(name, cell,
850 attr, col_id, NULL);
851 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
852 }
853
854 /****************************************************************************
855 Fill the supplied buffer with a short string representation of the given
856 value. Returned value is g_strdup'd and must be g_free'd.
857 ****************************************************************************/
propval_as_string(struct propval * pv)858 static gchar *propval_as_string(struct propval *pv)
859 {
860 int count = 0;
861
862 fc_assert_ret_val(NULL != pv, 0);
863
864 switch (pv->valtype) {
865 case VALTYPE_NONE:
866 return g_strdup("");
867
868 case VALTYPE_INT:
869 return g_strdup_printf("%d", pv->data.v_int);
870
871 case VALTYPE_BOOL:
872 return g_strdup_printf("%s", pv->data.v_bool ? _("TRUE") : _("FALSE"));
873
874 case VALTYPE_NATION:
875 return g_strdup_printf("%s", nation_adjective_translation(pv->data.v_nation));
876
877 case VALTYPE_GOV:
878 return g_strdup_printf("%s", government_name_translation(pv->data.v_gov));
879
880 case VALTYPE_BUILT_ARRAY:
881 {
882 int great_wonder_count = 0, small_wonder_count = 0, building_count = 0;
883 int id;
884
885 improvement_iterate(pimprove) {
886 id = improvement_index(pimprove);
887 if (pv->data.v_built[id].turn < 0) {
888 continue;
889 }
890 if (is_great_wonder(pimprove)) {
891 great_wonder_count++;
892 } else if (is_small_wonder(pimprove)) {
893 small_wonder_count++;
894 } else {
895 building_count++;
896 }
897 } improvement_iterate_end;
898 /* TRANS: "Number of buildings, number of small
899 * wonders (e.g. palace), number of great wonders." */
900 return g_strdup_printf(_("%db %ds %dW"),
901 building_count, small_wonder_count,
902 great_wonder_count);
903 }
904
905 case VALTYPE_INVENTIONS_ARRAY:
906 advance_index_iterate(A_FIRST, tech) {
907 if (BV_ISSET(pv->data.v_bv_inventions, tech)) {
908 count++;
909 }
910 } advance_index_iterate_end;
911 /* TRANS: "Number of technologies known". */
912 return g_strdup_printf(_("%d known"), count);
913
914 case VALTYPE_BV_SPECIAL:
915 extra_type_by_cause_iterate(EC_SPECIAL, spe) {
916 if (BV_ISSET(pv->data.v_bv_special, spe->data.special_idx)) {
917 count++;
918 }
919 } extra_type_by_cause_iterate_end;
920 /* TRANS: "The number of terrain specials (e.g. hut,
921 * river, pollution, etc.) present on a tile." */
922 return g_strdup_printf(_("%d present"), count);
923
924 case VALTYPE_BV_ROADS:
925 extra_type_by_cause_iterate(EC_ROAD, pextra) {
926 struct road_type *proad = extra_road_get(pextra);
927
928 if (BV_ISSET(pv->data.v_bv_roads, road_number(proad))) {
929 count++;
930 }
931 } extra_type_by_cause_iterate_end;
932 return g_strdup_printf(_("%d present"), count);
933
934 case VALTYPE_BV_BASES:
935 extra_type_by_cause_iterate(EC_BASE, pextra) {
936 struct base_type *pbase = extra_base_get(pextra);
937
938 if (BV_ISSET(pv->data.v_bv_bases, base_number(pbase))) {
939 count++;
940 }
941 } extra_type_by_cause_iterate_end;
942 return g_strdup_printf(_("%d present"), count);
943
944 case VALTYPE_NATION_HASH:
945 count = nation_hash_size(pv->data.v_nation_hash);
946 if (0 == count) {
947 return g_strdup(_("All nations"));
948 } else {
949 return g_strdup_printf(PL_("%d nation", "%d nations",
950 count), count);
951 }
952
953 case VALTYPE_STRING:
954 /* Assume it is a very long string. */
955 count = strlen(pv->data.v_const_string);
956 return g_strdup_printf(PL_("%d byte", "%d bytes", count),
957 count);
958
959 case VALTYPE_PIXBUF:
960 case VALTYPE_TILE_VISION_DATA:
961 break;
962 }
963
964 log_error("%s(): Unhandled value type %d for property value %p.",
965 __FUNCTION__, pv->valtype, pv);
966 return g_strdup("");
967 }
968
969 /****************************************************************************
970 Convert the built_status information to a user viewable string.
971 Returned value is g_strdup'd and must be g_free'd.
972 ****************************************************************************/
built_status_to_string(struct built_status * bs)973 static gchar *built_status_to_string(struct built_status *bs)
974 {
975 int turn_built;
976
977 turn_built = bs->turn;
978
979 if (turn_built == I_NEVER) {
980 /* TRANS: Improvement never built. */
981 return g_strdup(_("(never)"));
982 } else if (turn_built == I_DESTROYED) {
983 /* TRANS: Improvement was destroyed. */
984 return g_strdup(_("(destroyed)"));
985 } else {
986 return g_strdup_printf("%d", turn_built);
987 }
988 }
989
990 /****************************************************************************
991 Returns TRUE if a unit can be created at the given tile based on the
992 state of the editor (see editor_unit_virtual_create).
993 ****************************************************************************/
can_create_unit_at_tile(struct tile * ptile)994 static bool can_create_unit_at_tile(struct tile *ptile)
995 {
996 struct unit *vunit;
997 struct city *pcity;
998 struct player *pplayer;
999 bool ret;
1000
1001 if (!ptile) {
1002 return FALSE;
1003 }
1004
1005 vunit = editor_unit_virtual_create();
1006 if (!vunit) {
1007 return FALSE;
1008 }
1009
1010 pcity = tile_city(ptile);
1011 pplayer = unit_owner(vunit);
1012
1013 ret = (can_unit_exist_at_tile(vunit, ptile)
1014 && !is_non_allied_unit_tile(ptile, pplayer)
1015 && (pcity == NULL
1016 || pplayers_allied(city_owner(pcity),
1017 unit_owner(vunit))));
1018 free(vunit);
1019
1020 return ret;
1021 }
1022
1023 /****************************************************************************
1024 Return the next tag number in the sequence.
1025 ****************************************************************************/
get_next_unique_tag(void)1026 static int get_next_unique_tag(void)
1027 {
1028 static int tag_series = 0;
1029
1030 tag_series++;
1031 return tag_series;
1032 }
1033
1034 /****************************************************************************
1035 Return a newly allocated deep copy of the given value.
1036 ****************************************************************************/
propval_copy(struct propval * pv)1037 static struct propval *propval_copy(struct propval *pv)
1038 {
1039 struct propval *pv_copy;
1040 size_t size;
1041
1042 if (!pv) {
1043 return NULL;
1044 }
1045
1046 pv_copy = fc_calloc(1, sizeof(*pv));
1047 pv_copy->valtype = pv->valtype;
1048
1049 switch (pv->valtype) {
1050 case VALTYPE_NONE:
1051 return pv_copy;
1052 case VALTYPE_INT:
1053 pv_copy->data.v_int = pv->data.v_int;
1054 return pv_copy;
1055 case VALTYPE_BOOL:
1056 pv_copy->data.v_bool = pv->data.v_bool;
1057 return pv_copy;
1058 case VALTYPE_STRING:
1059 pv_copy->data.v_string = fc_strdup(pv->data.v_string);
1060 pv_copy->must_free = TRUE;
1061 return pv_copy;
1062 case VALTYPE_PIXBUF:
1063 g_object_ref(pv->data.v_pixbuf);
1064 pv_copy->data.v_pixbuf = pv->data.v_pixbuf;
1065 pv_copy->must_free = TRUE;
1066 return pv_copy;
1067 case VALTYPE_BUILT_ARRAY:
1068 size = B_LAST * sizeof(struct built_status);
1069 pv_copy->data.v_pointer = fc_malloc(size);
1070 memcpy(pv_copy->data.v_pointer, pv->data.v_pointer, size);
1071 pv_copy->must_free = TRUE;
1072 return pv_copy;
1073 case VALTYPE_BV_SPECIAL:
1074 pv_copy->data.v_bv_special = pv->data.v_bv_special;
1075 return pv_copy;
1076 case VALTYPE_BV_ROADS:
1077 pv_copy->data.v_bv_roads = pv->data.v_bv_roads;
1078 return pv_copy;
1079 case VALTYPE_BV_BASES:
1080 pv_copy->data.v_bv_bases = pv->data.v_bv_bases;
1081 return pv_copy;
1082 case VALTYPE_NATION:
1083 pv_copy->data.v_nation = pv->data.v_nation;
1084 return pv_copy;
1085 case VALTYPE_GOV:
1086 pv_copy->data.v_gov = pv->data.v_gov;
1087 return pv_copy;
1088 case VALTYPE_NATION_HASH:
1089 pv_copy->data.v_nation_hash
1090 = nation_hash_copy(pv->data.v_nation_hash);
1091 pv_copy->must_free = TRUE;
1092 return pv_copy;
1093 case VALTYPE_INVENTIONS_ARRAY:
1094 pv_copy->data.v_bv_inventions = pv->data.v_bv_inventions;
1095 return pv_copy;
1096 case VALTYPE_TILE_VISION_DATA:
1097 size = sizeof(struct tile_vision_data);
1098 pv_copy->data.v_tile_vision = fc_malloc(size);
1099 pv_copy->data.v_tile_vision->tile_known
1100 = pv->data.v_tile_vision->tile_known;
1101 vision_layer_iterate(v) {
1102 pv_copy->data.v_tile_vision->tile_seen[v]
1103 = pv->data.v_tile_vision->tile_seen[v];
1104 } vision_layer_iterate_end;
1105 pv_copy->must_free = TRUE;
1106 return pv_copy;
1107 }
1108
1109 log_error("%s(): Unhandled value type %d for property value %p.",
1110 __FUNCTION__, pv->valtype, pv);
1111 pv_copy->data = pv->data;
1112 return pv_copy;
1113 }
1114
1115 /****************************************************************************
1116 Free all allocated memory used by this property value, including calling
1117 the appropriate free function on the internal data according to its type.
1118 ****************************************************************************/
propval_free(struct propval * pv)1119 static void propval_free(struct propval *pv)
1120 {
1121 if (!pv) {
1122 return;
1123 }
1124
1125 propval_free_data(pv);
1126 free(pv);
1127 }
1128
1129 /****************************************************************************
1130 Frees the internal data held by the propval, without freeing the propval
1131 struct itself.
1132 ****************************************************************************/
propval_free_data(struct propval * pv)1133 static void propval_free_data(struct propval *pv)
1134 {
1135 if (!pv || !pv->must_free) {
1136 return;
1137 }
1138
1139 switch (pv->valtype) {
1140 case VALTYPE_NONE:
1141 case VALTYPE_INT:
1142 case VALTYPE_BOOL:
1143 case VALTYPE_BV_SPECIAL:
1144 case VALTYPE_BV_ROADS:
1145 case VALTYPE_BV_BASES:
1146 case VALTYPE_NATION:
1147 case VALTYPE_GOV:
1148 return;
1149 case VALTYPE_PIXBUF:
1150 g_object_unref(pv->data.v_pixbuf);
1151 return;
1152 case VALTYPE_STRING:
1153 case VALTYPE_BUILT_ARRAY:
1154 case VALTYPE_INVENTIONS_ARRAY:
1155 case VALTYPE_TILE_VISION_DATA:
1156 free(pv->data.v_pointer);
1157 return;
1158 case VALTYPE_NATION_HASH:
1159 nation_hash_destroy(pv->data.v_nation_hash);
1160 return;
1161 }
1162
1163 log_error("%s(): Unhandled request to free data %p (type %s).",
1164 __FUNCTION__, pv->data.v_pointer, valtype_get_name(pv->valtype));
1165 }
1166
1167 /****************************************************************************
1168 Returns TRUE if the two values are equal, in a deep sense.
1169 ****************************************************************************/
propval_equal(struct propval * pva,struct propval * pvb)1170 static bool propval_equal(struct propval *pva,
1171 struct propval *pvb)
1172 {
1173 if (!pva || !pvb) {
1174 return pva == pvb;
1175 }
1176
1177 if (pva->valtype != pvb->valtype) {
1178 return FALSE;
1179 }
1180
1181 switch (pva->valtype) {
1182 case VALTYPE_NONE:
1183 return TRUE;
1184 case VALTYPE_INT:
1185 return pva->data.v_int == pvb->data.v_int;
1186 case VALTYPE_BOOL:
1187 return pva->data.v_bool == pvb->data.v_bool;
1188 case VALTYPE_STRING:
1189 if (pva->data.v_const_string && pvb->data.v_const_string) {
1190 return 0 == strcmp(pva->data.v_const_string,
1191 pvb->data.v_const_string);
1192 }
1193 return pva->data.v_const_string == pvb->data.v_const_string;
1194 case VALTYPE_PIXBUF:
1195 return pva->data.v_pixbuf == pvb->data.v_pixbuf;
1196 case VALTYPE_BUILT_ARRAY:
1197 if (pva->data.v_pointer == pvb->data.v_pointer) {
1198 return TRUE;
1199 } else if (!pva->data.v_pointer || !pvb->data.v_pointer) {
1200 return FALSE;
1201 }
1202
1203 improvement_iterate(pimprove) {
1204 int id, vatb, vbtb;
1205 id = improvement_index(pimprove);
1206 vatb = pva->data.v_built[id].turn;
1207 vbtb = pvb->data.v_built[id].turn;
1208 if (vatb < 0 && vbtb < 0) {
1209 continue;
1210 }
1211 if (vatb != vbtb) {
1212 return FALSE;
1213 }
1214 } improvement_iterate_end;
1215 return TRUE;
1216 case VALTYPE_INVENTIONS_ARRAY:
1217 return BV_ARE_EQUAL(pva->data.v_bv_inventions, pvb->data.v_bv_inventions);
1218 case VALTYPE_BV_SPECIAL:
1219 return BV_ARE_EQUAL(pva->data.v_bv_special, pvb->data.v_bv_special);
1220 case VALTYPE_BV_ROADS:
1221 return BV_ARE_EQUAL(pva->data.v_bv_roads, pvb->data.v_bv_roads);
1222 case VALTYPE_BV_BASES:
1223 return BV_ARE_EQUAL(pva->data.v_bv_bases, pvb->data.v_bv_bases);
1224 case VALTYPE_NATION:
1225 return pva->data.v_nation == pvb->data.v_nation;
1226 case VALTYPE_NATION_HASH:
1227 return nation_hashs_are_equal(pva->data.v_nation_hash,
1228 pvb->data.v_nation_hash);
1229 case VALTYPE_GOV:
1230 return pva->data.v_gov == pvb->data.v_gov;
1231 case VALTYPE_TILE_VISION_DATA:
1232 if (!BV_ARE_EQUAL(pva->data.v_tile_vision->tile_known,
1233 pvb->data.v_tile_vision->tile_known)) {
1234 return FALSE;
1235 }
1236 vision_layer_iterate(v) {
1237 if (!BV_ARE_EQUAL(pva->data.v_tile_vision->tile_seen[v],
1238 pvb->data.v_tile_vision->tile_seen[v])) {
1239 return FALSE;
1240 }
1241 } vision_layer_iterate_end;
1242 return TRUE;
1243 }
1244
1245 log_error("%s(): Unhandled value type %d for property values %p and %p.",
1246 __FUNCTION__, pva->valtype, pva, pvb);
1247 return pva->data.v_pointer == pvb->data.v_pointer;
1248 }
1249
1250 /****************************************************************************
1251 Create a new "property state" record. It keeps track of the modified value
1252 of a property bound to an object.
1253
1254 NB: Does NOT make a copy of 'pv'.
1255 ****************************************************************************/
propstate_new(struct objprop * op,struct propval * pv)1256 static struct propstate *propstate_new(struct objprop *op,
1257 struct propval *pv)
1258 {
1259 struct propstate *ps;
1260
1261 if (!op) {
1262 return NULL;
1263 }
1264
1265 ps = fc_calloc(1, sizeof(*ps));
1266 ps->property_id = objprop_get_id(op);
1267 ps->property_value = pv;
1268
1269 return ps;
1270 }
1271
1272 /****************************************************************************
1273 Removes the stored value, freeing it if needed.
1274 ****************************************************************************/
propstate_clear_value(struct propstate * ps)1275 static void propstate_clear_value(struct propstate *ps)
1276 {
1277 if (!ps) {
1278 return;
1279 }
1280
1281 propval_free(ps->property_value);
1282 ps->property_value = NULL;
1283 }
1284
1285 /****************************************************************************
1286 Free a property state and any associated resources.
1287 ****************************************************************************/
propstate_destroy(struct propstate * ps)1288 static void propstate_destroy(struct propstate *ps)
1289 {
1290 if (!ps) {
1291 return;
1292 }
1293 propstate_clear_value(ps);
1294 free(ps);
1295 }
1296
1297 /****************************************************************************
1298 Replace the stored property value with a new one. The old value will
1299 be freed if needed.
1300
1301 NB: Does NOT make a copy of 'pv'.
1302 ****************************************************************************/
propstate_set_value(struct propstate * ps,struct propval * pv)1303 static void propstate_set_value(struct propstate *ps,
1304 struct propval *pv)
1305 {
1306 if (!ps) {
1307 return;
1308 }
1309 propstate_clear_value(ps);
1310 ps->property_value = pv;
1311 }
1312
1313 /****************************************************************************
1314 Returns the stored value.
1315
1316 NB: NOT a copy of the value.
1317 ****************************************************************************/
propstate_get_value(struct propstate * ps)1318 static struct propval *propstate_get_value(struct propstate *ps)
1319 {
1320 if (!ps) {
1321 return NULL;
1322 }
1323 return ps->property_value;
1324 }
1325
1326 /****************************************************************************
1327 Create a new object "bind". It serves to bind a set of object properties
1328 to an object instance.
1329 ****************************************************************************/
objbind_new(enum editor_object_type objtype,gpointer object)1330 static struct objbind *objbind_new(enum editor_object_type objtype,
1331 gpointer object)
1332 {
1333 struct objbind *ob;
1334 int id;
1335
1336 if (object == NULL) {
1337 return NULL;
1338 }
1339
1340 id = objtype_get_id_from_object(objtype, object);
1341 if (id < 0) {
1342 return NULL;
1343 }
1344
1345 ob = fc_calloc(1, sizeof(*ob));
1346 ob->object_id = id;
1347 ob->objtype = objtype;
1348 ob->propstate_table = propstate_hash_new();
1349
1350 return ob;
1351 }
1352
1353 /****************************************************************************
1354 Returns the bound object, if it still "exists". Returns NULL on error.
1355 ****************************************************************************/
objbind_get_object(struct objbind * ob)1356 static gpointer objbind_get_object(struct objbind *ob)
1357 {
1358 int id;
1359 if (!ob) {
1360 return NULL;
1361 }
1362
1363 id = objbind_get_object_id(ob);
1364
1365 return objtype_get_object_from_id(ob->objtype, id);
1366 }
1367
1368 /****************************************************************************
1369 Returns the ID of the bound object, or -1 if invalid.
1370 ****************************************************************************/
objbind_get_object_id(struct objbind * ob)1371 static int objbind_get_object_id(struct objbind *ob)
1372 {
1373 if (NULL == ob) {
1374 return -1;
1375 }
1376 return ob->object_id;
1377 }
1378
1379 /****************************************************************************
1380 Sends a request to the server to have the bound object erased from
1381 existence. Only makes sense for object types for which the function
1382 objtype_is_conserved() returns FALSE.
1383 ****************************************************************************/
objbind_request_destroy_object(struct objbind * ob)1384 static void objbind_request_destroy_object(struct objbind *ob)
1385 {
1386 struct connection *my_conn = &client.conn;
1387 enum editor_object_type objtype;
1388 int id;
1389
1390 if (!ob) {
1391 return;
1392 }
1393
1394 objtype = objbind_get_objtype(ob);
1395 if (objtype_is_conserved(objtype)) {
1396 return;
1397 }
1398
1399 id = objbind_get_object_id(ob);
1400
1401 switch (objtype) {
1402 case OBJTYPE_STARTPOS:
1403 dsend_packet_edit_startpos(my_conn, id, TRUE, 0);
1404 return;
1405 case OBJTYPE_UNIT:
1406 dsend_packet_edit_unit_remove_by_id(my_conn, id);
1407 return;
1408 case OBJTYPE_CITY:
1409 dsend_packet_edit_city_remove(my_conn, id);
1410 return;
1411 case OBJTYPE_PLAYER:
1412 dsend_packet_edit_player_remove(my_conn, id);
1413 return;
1414 case OBJTYPE_TILE:
1415 case OBJTYPE_GAME:
1416 case NUM_OBJTYPES:
1417 break;
1418 }
1419
1420 log_error("%s(): Unhandled request to destroy object %p (ID %d) of type "
1421 "%d (%s).", __FUNCTION__, objbind_get_object(ob), id, objtype,
1422 objtype_get_name(objtype));
1423 }
1424
1425 /****************************************************************************
1426 Returns a newly allocated property value for the given object property
1427 on the object referenced by the given object bind, or NULL on failure.
1428
1429 NB: You must call propval_free on the non-NULL return value when it
1430 no longer needed.
1431 ****************************************************************************/
objbind_get_value_from_object(struct objbind * ob,struct objprop * op)1432 static struct propval *objbind_get_value_from_object(struct objbind *ob,
1433 struct objprop *op)
1434 {
1435 enum editor_object_type objtype;
1436 enum object_property_ids propid;
1437 struct propval *pv;
1438
1439 if (!ob || !op) {
1440 return NULL;
1441 }
1442
1443 objtype = objbind_get_objtype(ob);
1444 propid = objprop_get_id(op);
1445
1446 pv = fc_calloc(1, sizeof(*pv));
1447 pv->valtype = objprop_get_valtype(op);
1448
1449 switch (objtype) {
1450 case OBJTYPE_TILE:
1451 {
1452 const struct tile *ptile = objbind_get_object(ob);
1453 int tile_x, tile_y, nat_x, nat_y;
1454
1455 if (NULL == ptile) {
1456 goto FAILED;
1457 }
1458
1459 index_to_map_pos(&tile_x, &tile_y, tile_index(ptile));
1460 index_to_native_pos(&nat_x, &nat_y, tile_index(ptile));
1461
1462 switch (propid) {
1463 case OPID_TILE_IMAGE:
1464 pv->data.v_pixbuf = create_tile_pixbuf(ptile);
1465 pv->must_free = TRUE;
1466 break;
1467 #ifdef DEBUG
1468 case OPID_TILE_ADDRESS:
1469 pv->data.v_string = g_strdup_printf("%p", ptile);
1470 pv->must_free = TRUE;
1471 break;
1472 #endif /* DEBUG */
1473 case OPID_TILE_TERRAIN:
1474 {
1475 const struct terrain *pterrain = tile_terrain(ptile);
1476
1477 if (NULL != pterrain) {
1478 pv->data.v_const_string = terrain_name_translation(pterrain);
1479 } else {
1480 pv->data.v_const_string = "";
1481 }
1482 }
1483 break;
1484 case OPID_TILE_RESOURCE:
1485 {
1486 const struct resource *presource = tile_resource(ptile);
1487
1488 if (NULL != presource) {
1489 pv->data.v_const_string = resource_name_translation(presource);
1490 } else {
1491 pv->data.v_const_string = "";
1492 }
1493 }
1494 break;
1495 case OPID_TILE_XY:
1496 pv->data.v_string = g_strdup_printf("(%d, %d)", tile_x, tile_y);
1497 pv->must_free = TRUE;
1498 break;
1499 case OPID_TILE_INDEX:
1500 pv->data.v_int = tile_index(ptile);
1501 break;
1502 case OPID_TILE_X:
1503 pv->data.v_int = tile_x;
1504 break;
1505 case OPID_TILE_Y:
1506 pv->data.v_int = tile_y;
1507 break;
1508 case OPID_TILE_NAT_X:
1509 pv->data.v_int = nat_x;
1510 break;
1511 case OPID_TILE_NAT_Y:
1512 pv->data.v_int = nat_y;
1513 break;
1514 case OPID_TILE_CONTINENT:
1515 pv->data.v_int = ptile->continent;
1516 break;
1517 case OPID_TILE_SPECIALS:
1518 BV_CLR_ALL(pv->data.v_bv_special);
1519 extra_type_by_cause_iterate(EC_SPECIAL, pextra) {
1520 if (tile_has_extra(ptile, pextra)) {
1521 BV_SET(pv->data.v_bv_special, pextra->data.special_idx);
1522 }
1523 } extra_type_by_cause_iterate_end;
1524 break;
1525 case OPID_TILE_ROADS:
1526 BV_CLR_ALL(pv->data.v_bv_roads);
1527 extra_type_by_cause_iterate(EC_ROAD, pextra) {
1528 if (tile_has_extra(ptile, pextra)) {
1529 BV_SET(pv->data.v_bv_roads, road_index(extra_road_get(pextra)));
1530 }
1531 } extra_type_by_cause_iterate_end;
1532 break;
1533 case OPID_TILE_BASES:
1534 BV_CLR_ALL(pv->data.v_bv_bases);
1535 extra_type_by_cause_iterate(EC_BASE, pextra) {
1536 if (tile_has_extra(ptile, pextra)) {
1537 BV_SET(pv->data.v_bv_bases, base_index(extra_base_get(pextra)));
1538 }
1539 } extra_type_by_cause_iterate_end;
1540 break;
1541 case OPID_TILE_VISION:
1542 pv->data.v_tile_vision = fc_malloc(sizeof(struct tile_vision_data));
1543
1544 /* The server saves the known tiles and the player vision in special
1545 * bitvectors with the number of tiles as index. Here we want the
1546 * information for one tile. Thus, the data is transformed to
1547 * bitvectors with the number of player slots as index. */
1548 BV_CLR_ALL(pv->data.v_tile_vision->tile_known);
1549 players_iterate(pplayer) {
1550 if (dbv_isset(&pplayer->tile_known, tile_index(ptile))) {
1551 BV_SET(pv->data.v_tile_vision->tile_known,
1552 player_index(pplayer));
1553 }
1554 } players_iterate_end;
1555
1556 vision_layer_iterate(v) {
1557 BV_CLR_ALL(pv->data.v_tile_vision->tile_seen[v]);
1558 players_iterate(pplayer) {
1559 if (fc_funcs->player_tile_vision_get(ptile, pplayer, v)) {
1560 BV_SET(pv->data.v_tile_vision->tile_seen[v],
1561 player_index(pplayer));
1562 }
1563 } players_iterate_end;
1564 } vision_layer_iterate_end;
1565 pv->must_free = TRUE;
1566 break;
1567 case OPID_TILE_LABEL:
1568 if (ptile->label != NULL) {
1569 pv->data.v_const_string = ptile->label;
1570 } else {
1571 pv->data.v_const_string = "";
1572 }
1573 break;
1574 default:
1575 log_error("%s(): Unhandled request for value of property %d "
1576 "(%s) from object of type \"%s\".", __FUNCTION__,
1577 propid, objprop_get_name(op), objtype_get_name(objtype));
1578 goto FAILED;
1579 }
1580 }
1581 return pv;
1582
1583 case OBJTYPE_STARTPOS:
1584 {
1585 const struct startpos *psp = objbind_get_object(ob);
1586 const struct tile *ptile;
1587
1588 if (NULL == psp) {
1589 goto FAILED;
1590 }
1591
1592 switch (propid) {
1593 case OPID_STARTPOS_IMAGE:
1594 ptile = startpos_tile(psp);
1595 pv->data.v_pixbuf = create_tile_pixbuf(ptile);
1596 pv->must_free = TRUE;
1597 break;
1598 case OPID_STARTPOS_XY:
1599 ptile = startpos_tile(psp);
1600 pv->data.v_string = g_strdup_printf("(%d, %d)", TILE_XY(ptile));
1601 pv->must_free = TRUE;
1602 break;
1603 case OPID_STARTPOS_EXCLUDE:
1604 pv->data.v_bool = startpos_is_excluding(psp);
1605 break;
1606 case OPID_STARTPOS_NATIONS:
1607 pv->data.v_nation_hash = nation_hash_copy(startpos_raw_nations(psp));
1608 pv->must_free = TRUE;
1609 break;
1610 default:
1611 log_error("%s(): Unhandled request for value of property %d "
1612 "(%s) from object of type \"%s\".", __FUNCTION__,
1613 propid, objprop_get_name(op), objtype_get_name(objtype));
1614 goto FAILED;
1615 }
1616 }
1617 return pv;
1618
1619 case OBJTYPE_UNIT:
1620 {
1621 struct unit *punit = objbind_get_object(ob);
1622
1623 if (NULL == punit) {
1624 goto FAILED;
1625 }
1626
1627 switch (propid) {
1628 case OPID_UNIT_IMAGE:
1629 pv->data.v_pixbuf = create_unit_pixbuf(punit);
1630 pv->must_free = TRUE;
1631 break;
1632 #ifdef DEBUG
1633 case OPID_UNIT_ADDRESS:
1634 pv->data.v_string = g_strdup_printf("%p", punit);
1635 pv->must_free = TRUE;
1636 break;
1637 #endif /* DEBUG */
1638 case OPID_UNIT_XY:
1639 {
1640 const struct tile *ptile = unit_tile(punit);
1641
1642 pv->data.v_string = g_strdup_printf("(%d, %d)", TILE_XY(ptile));
1643 pv->must_free = TRUE;
1644 }
1645 break;
1646 case OPID_UNIT_ID:
1647 pv->data.v_int = punit->id;
1648 break;
1649 case OPID_UNIT_TYPE:
1650 {
1651 const struct unit_type *putype = unit_type_get(punit);
1652
1653 pv->data.v_const_string = utype_name_translation(putype);
1654 }
1655 break;
1656 case OPID_UNIT_MOVES_LEFT:
1657 pv->data.v_int = punit->moves_left;
1658 break;
1659 case OPID_UNIT_FUEL:
1660 pv->data.v_int = punit->fuel;
1661 break;
1662 case OPID_UNIT_MOVED:
1663 pv->data.v_bool = punit->moved;
1664 break;
1665 case OPID_UNIT_DONE_MOVING:
1666 pv->data.v_bool = punit->done_moving;
1667 break;
1668 case OPID_UNIT_HP:
1669 pv->data.v_int = punit->hp;
1670 break;
1671 case OPID_UNIT_VETERAN:
1672 pv->data.v_int = punit->veteran;
1673 break;
1674 default:
1675 log_error("%s(): Unhandled request for value of property %d "
1676 "(%s) from object of type \"%s\".", __FUNCTION__,
1677 propid, objprop_get_name(op), objtype_get_name(objtype));
1678 goto FAILED;
1679 }
1680 }
1681 return pv;
1682
1683 case OBJTYPE_CITY:
1684 {
1685 const struct city *pcity = objbind_get_object(ob);
1686
1687 if (NULL == pcity) {
1688 goto FAILED;
1689 }
1690
1691 switch (propid) {
1692 case OPID_CITY_IMAGE:
1693 pv->data.v_pixbuf = create_city_pixbuf(pcity);
1694 pv->must_free = TRUE;
1695 break;
1696 #ifdef DEBUG
1697 case OPID_CITY_ADDRESS:
1698 pv->data.v_string = g_strdup_printf("%p", pcity);
1699 pv->must_free = TRUE;
1700 break;
1701 #endif /* DEBUG */
1702 case OPID_CITY_XY:
1703 {
1704 const struct tile *ptile = city_tile(pcity);
1705
1706 pv->data.v_string = g_strdup_printf("(%d, %d)", TILE_XY(ptile));
1707 pv->must_free = TRUE;
1708 }
1709 break;
1710 case OPID_CITY_ID:
1711 pv->data.v_int = pcity->id;
1712 break;
1713 case OPID_CITY_NAME:
1714 pv->data.v_const_string = pcity->name;
1715 break;
1716 case OPID_CITY_SIZE:
1717 pv->data.v_int = city_size_get(pcity);
1718 break;
1719 case OPID_CITY_HISTORY:
1720 pv->data.v_int = pcity->history;
1721 break;
1722 case OPID_CITY_BUILDINGS:
1723 pv->data.v_built = fc_malloc(sizeof(pcity->built));
1724 memcpy(pv->data.v_built, pcity->built, sizeof(pcity->built));
1725 pv->must_free = TRUE;
1726 break;
1727 case OPID_CITY_FOOD_STOCK:
1728 pv->data.v_int = pcity->food_stock;
1729 break;
1730 case OPID_CITY_SHIELD_STOCK:
1731 pv->data.v_int = pcity->shield_stock;
1732 break;
1733 default:
1734 log_error("%s(): Unhandled request for value of property %d "
1735 "(%s) from object of type \"%s\".", __FUNCTION__,
1736 propid, objprop_get_name(op), objtype_get_name(objtype));
1737 goto FAILED;
1738 }
1739 }
1740 return pv;
1741
1742 case OBJTYPE_PLAYER:
1743 {
1744 const struct player *pplayer = objbind_get_object(ob);
1745 const struct research *presearch;
1746
1747 if (NULL == pplayer) {
1748 goto FAILED;
1749 }
1750
1751 switch (propid) {
1752 case OPID_PLAYER_NAME:
1753 pv->data.v_const_string = pplayer->name;
1754 break;
1755 case OPID_PLAYER_NATION:
1756 pv->data.v_nation = nation_of_player(pplayer);
1757 break;
1758 case OPID_PLAYER_GOV:
1759 pv->data.v_gov = pplayer->government;
1760 break;
1761 case OPID_PLAYER_AGE:
1762 pv->data.v_int = pplayer->turns_alive;
1763 break;
1764 #ifdef DEBUG
1765 case OPID_PLAYER_ADDRESS:
1766 pv->data.v_string = g_strdup_printf("%p", pplayer);
1767 pv->must_free = TRUE;
1768 break;
1769 #endif /* DEBUG */
1770 case OPID_PLAYER_INVENTIONS:
1771 presearch = research_get(pplayer);
1772 BV_CLR_ALL(pv->data.v_bv_inventions);
1773 advance_index_iterate(A_FIRST, tech) {
1774 if (TECH_KNOWN == research_invention_state(presearch, tech)) {
1775 BV_SET(pv->data.v_bv_inventions, tech);
1776 }
1777 } advance_index_iterate_end;
1778 break;
1779 case OPID_PLAYER_SCIENCE:
1780 presearch = research_get(pplayer);
1781 pv->data.v_int = presearch->bulbs_researched;
1782 break;
1783 case OPID_PLAYER_GOLD:
1784 pv->data.v_int = pplayer->economic.gold;
1785 break;
1786 default:
1787 log_error("%s(): Unhandled request for value of property %d "
1788 "(%s) from object of type \"%s\".", __FUNCTION__,
1789 propid, objprop_get_name(op), objtype_get_name(objtype));
1790 goto FAILED;
1791 }
1792 }
1793 return pv;
1794
1795 case OBJTYPE_GAME:
1796 {
1797 const struct civ_game *pgame = objbind_get_object(ob);
1798
1799 if (NULL == pgame) {
1800 goto FAILED;
1801 }
1802
1803 switch (propid) {
1804 case OPID_GAME_YEAR:
1805 pv->data.v_int = pgame->info.year32;
1806 break;
1807 case OPID_GAME_SCENARIO:
1808 pv->data.v_bool = pgame->scenario.is_scenario;
1809 break;
1810 case OPID_GAME_SCENARIO_NAME:
1811 pv->data.v_const_string = pgame->scenario.name;
1812 break;
1813 case OPID_GAME_SCENARIO_AUTHORS:
1814 pv->data.v_const_string = pgame->scenario.authors;
1815 break;
1816 case OPID_GAME_SCENARIO_DESC:
1817 pv->data.v_const_string = pgame->scenario_desc.description;
1818 break;
1819 case OPID_GAME_SCENARIO_RANDSTATE:
1820 pv->data.v_bool = pgame->scenario.save_random;
1821 break;
1822 case OPID_GAME_SCENARIO_PLAYERS:
1823 pv->data.v_bool = pgame->scenario.players;
1824 break;
1825 case OPID_GAME_STARTPOS_NATIONS:
1826 pv->data.v_bool = pgame->scenario.startpos_nations;
1827 break;
1828 case OPID_GAME_PREVENT_CITIES:
1829 pv->data.v_bool = pgame->scenario.prevent_new_cities;
1830 break;
1831 case OPID_GAME_LAKE_FLOODING:
1832 pv->data.v_bool = pgame->scenario.lake_flooding;
1833 break;
1834 default:
1835 log_error("%s(): Unhandled request for value of property %d "
1836 "(%s) from object of type \"%s\".", __FUNCTION__,
1837 propid, objprop_get_name(op), objtype_get_name(objtype));
1838 goto FAILED;
1839 }
1840 }
1841 return pv;
1842
1843 case NUM_OBJTYPES:
1844 break;
1845 }
1846
1847 log_error("%s(): Unhandled request for object type \"%s\" (nb %d).",
1848 __FUNCTION__, objtype_get_name(objtype), objtype);
1849
1850 FAILED:
1851 if (NULL != pv) {
1852 free(pv);
1853 }
1854 return NULL;
1855 }
1856
1857 /****************************************************************************
1858 If applicable, sets the allowed range values of the given object property
1859 as applied to the bound object. Returns TRUE if values were set.
1860 ****************************************************************************/
objbind_get_allowed_value_span(struct objbind * ob,struct objprop * op,double * pmin,double * pmax,double * pstep,double * pbig_step)1861 static bool objbind_get_allowed_value_span(struct objbind *ob,
1862 struct objprop *op,
1863 double *pmin,
1864 double *pmax,
1865 double *pstep,
1866 double *pbig_step)
1867 {
1868 enum editor_object_type objtype;
1869 enum object_property_ids propid;
1870 double dummy;
1871
1872 /* Fill the values with something. */
1873 if (NULL != pmin) {
1874 *pmin = 0;
1875 } else {
1876 pmin = &dummy;
1877 }
1878 if (NULL != pmax) {
1879 *pmax = 1;
1880 } else {
1881 pmax = &dummy;
1882 }
1883 if (NULL != pstep) {
1884 *pstep = 1;
1885 } else {
1886 pstep = &dummy;
1887 }
1888 if (NULL != pbig_step) {
1889 *pbig_step = 1;
1890 } else {
1891 pbig_step = &dummy;
1892 }
1893
1894 if (!ob || !op) {
1895 return FALSE;
1896 }
1897
1898 propid = objprop_get_id(op);
1899 objtype = objbind_get_objtype(ob);
1900
1901 switch (objtype) {
1902 case OBJTYPE_TILE:
1903 case OBJTYPE_STARTPOS:
1904 log_error("%s(): Unhandled request for value range of property %d (%s) "
1905 "from object of type \"%s\".", __FUNCTION__,
1906 propid, objprop_get_name(op), objtype_get_name(objtype));
1907 return FALSE;
1908
1909 case OBJTYPE_UNIT:
1910 {
1911 const struct unit *punit = objbind_get_object(ob);
1912 const struct unit_type *putype;
1913
1914 if (NULL == punit) {
1915 return FALSE;
1916 }
1917
1918 putype = unit_type_get(punit);
1919
1920 switch (propid) {
1921 case OPID_UNIT_MOVES_LEFT:
1922 *pmin = 0;
1923 *pmax = MAX_MOVE_FRAGS;
1924 *pstep = 1;
1925 *pbig_step = 5;
1926 return TRUE;
1927 case OPID_UNIT_FUEL:
1928 *pmin = 0;
1929 *pmax = utype_fuel(putype);
1930 *pstep = 1;
1931 *pbig_step = 5;
1932 return TRUE;
1933 case OPID_UNIT_HP:
1934 *pmin = 1;
1935 *pmax = putype->hp;
1936 *pstep = 1;
1937 *pbig_step = 10;
1938 return TRUE;
1939 case OPID_UNIT_VETERAN:
1940 *pmin = 0;
1941 *pmax = utype_veteran_levels(putype) - 1;
1942 *pstep = 1;
1943 *pbig_step = 3;
1944 return TRUE;
1945 default:
1946 break;
1947 }
1948 }
1949 log_error("%s(): Unhandled request for value range of property %d (%s) "
1950 "from object of type \"%s\".", __FUNCTION__,
1951 propid, objprop_get_name(op), objtype_get_name(objtype));
1952 return FALSE;
1953
1954 case OBJTYPE_CITY:
1955 {
1956 const struct city *pcity = objbind_get_object(ob);
1957
1958 if (NULL == pcity) {
1959 return FALSE;
1960 }
1961
1962 switch (propid) {
1963 case OPID_CITY_SIZE:
1964 *pmin = 1;
1965 *pmax = MAX_CITY_SIZE;
1966 *pstep = 1;
1967 *pbig_step = 5;
1968 return TRUE;
1969 case OPID_CITY_HISTORY:
1970 *pmin = 0;
1971 *pmax = USHRT_MAX;
1972 *pstep = 1;
1973 *pbig_step = 10;
1974 return TRUE;
1975 case OPID_CITY_FOOD_STOCK:
1976 *pmin = 0;
1977 *pmax = city_granary_size(city_size_get(pcity));
1978 *pstep = 1;
1979 *pbig_step = 5;
1980 return TRUE;
1981 case OPID_CITY_SHIELD_STOCK:
1982 *pmin = 0;
1983 *pmax = USHRT_MAX; /* Limited to uint16 by city info packet. */
1984 *pstep = 1;
1985 *pbig_step = 10;
1986 return TRUE;
1987 default:
1988 break;
1989 }
1990 }
1991 log_error("%s(): Unhandled request for value range of property %d (%s) "
1992 "from object of type \"%s\".", __FUNCTION__,
1993 propid, objprop_get_name(op), objtype_get_name(objtype));
1994 return FALSE;
1995
1996 case OBJTYPE_PLAYER:
1997 switch (propid) {
1998 case OPID_PLAYER_SCIENCE:
1999 *pmin = 0;
2000 *pmax = 1000000; /* Arbitrary. */
2001 *pstep = 1;
2002 *pbig_step = 100;
2003 return TRUE;
2004 case OPID_PLAYER_GOLD:
2005 *pmin = 0;
2006 *pmax = 1000000; /* Arbitrary. */
2007 *pstep = 1;
2008 *pbig_step = 100;
2009 return TRUE;
2010 default:
2011 break;
2012 }
2013 log_error("%s(): Unhandled request for value range of property %d (%s) "
2014 "from object of type \"%s\".", __FUNCTION__,
2015 propid, objprop_get_name(op), objtype_get_name(objtype));
2016 return FALSE;
2017
2018 case OBJTYPE_GAME:
2019 switch (propid) {
2020 case OPID_GAME_YEAR:
2021 *pmin = -30000;
2022 *pmax = 30000;
2023 *pstep = 1;
2024 *pbig_step = 25;
2025 return TRUE;
2026 default:
2027 break;
2028 }
2029 log_error("%s(): Unhandled request for value range of property %d (%s) "
2030 "from object of type \"%s\".", __FUNCTION__,
2031 propid, objprop_get_name(op), objtype_get_name(objtype));
2032 return FALSE;
2033
2034 case NUM_OBJTYPES:
2035 break;
2036 }
2037
2038 log_error("%s(): Unhandled request for object type \"%s\" (nb %d).",
2039 __FUNCTION__, objtype_get_name(objtype), objtype);
2040 return FALSE;
2041 }
2042
2043 /****************************************************************************
2044 Remove a stored modified value, if it exists.
2045 ****************************************************************************/
objbind_clear_modified_value(struct objbind * ob,struct objprop * op)2046 static void objbind_clear_modified_value(struct objbind *ob,
2047 struct objprop *op)
2048 {
2049 if (!ob || !op || !ob->propstate_table) {
2050 return;
2051 }
2052
2053 propstate_hash_remove(ob->propstate_table, objprop_get_id(op));
2054 }
2055
2056 /****************************************************************************
2057 Returns TRUE if a stored modified property value exists for this bound
2058 object for the given property.
2059 ****************************************************************************/
objbind_property_is_modified(struct objbind * ob,struct objprop * op)2060 static bool objbind_property_is_modified(struct objbind *ob,
2061 struct objprop *op)
2062 {
2063 if (!ob || !op) {
2064 return FALSE;
2065 }
2066
2067 if (objprop_is_readonly(op)) {
2068 return FALSE;
2069 }
2070
2071 return propstate_hash_lookup(ob->propstate_table,
2072 objprop_get_id(op), NULL);
2073 }
2074
2075 /****************************************************************************
2076 Returns TRUE if there are any stored modified values of any of the
2077 properties of the bound object.
2078 ****************************************************************************/
objbind_has_modified_properties(struct objbind * ob)2079 static bool objbind_has_modified_properties(struct objbind *ob)
2080 {
2081 if (!ob) {
2082 return FALSE;
2083 }
2084
2085 return (0 < propstate_hash_size(ob->propstate_table));
2086 }
2087
2088 /****************************************************************************
2089 Deletes all stored modified property values.
2090 ****************************************************************************/
objbind_clear_all_modified_values(struct objbind * ob)2091 static void objbind_clear_all_modified_values(struct objbind *ob)
2092 {
2093 if (!ob) {
2094 return;
2095 }
2096 propstate_hash_clear(ob->propstate_table);
2097 }
2098
2099 /****************************************************************************
2100 Store a modified property value, but only if it is different from the
2101 current value. Always makes a copy of the given value when storing.
2102 ****************************************************************************/
objbind_set_modified_value(struct objbind * ob,struct objprop * op,struct propval * pv)2103 static void objbind_set_modified_value(struct objbind *ob,
2104 struct objprop *op,
2105 struct propval *pv)
2106 {
2107 struct propstate *ps;
2108 bool equal;
2109 struct propval *pv_old, *pv_copy;
2110 enum object_property_ids propid;
2111
2112 if (!ob || !op) {
2113 return;
2114 }
2115
2116 propid = objprop_get_id(op);
2117
2118 pv_old = objbind_get_value_from_object(ob, op);
2119 if (!pv_old) {
2120 return;
2121 }
2122
2123 equal = propval_equal(pv, pv_old);
2124 propval_free(pv_old);
2125
2126 if (equal) {
2127 objbind_clear_modified_value(ob, op);
2128 return;
2129 }
2130
2131 pv_copy = propval_copy(pv);
2132
2133 if (propstate_hash_lookup(ob->propstate_table, propid, &ps)) {
2134 propstate_set_value(ps, pv_copy);
2135 } else {
2136 ps = propstate_new(op, pv_copy);
2137 propstate_hash_insert(ob->propstate_table, propid, ps);
2138 }
2139 }
2140
2141 /****************************************************************************
2142 Retrieve the stored property value for the bound object, or NULL if none
2143 exists.
2144
2145 NB: Does NOT return a copy.
2146 ****************************************************************************/
objbind_get_modified_value(struct objbind * ob,struct objprop * op)2147 static struct propval *objbind_get_modified_value(struct objbind *ob,
2148 struct objprop *op)
2149 {
2150 struct propstate *ps;
2151
2152 if (!ob || !op) {
2153 return FALSE;
2154 }
2155
2156 if (propstate_hash_lookup(ob->propstate_table, objprop_get_id(op), &ps)) {
2157 return propstate_get_value(ps);
2158 } else {
2159 return NULL;
2160 }
2161 }
2162
2163 /****************************************************************************
2164 Destroy the object bind and free any resources it might have been using.
2165 ****************************************************************************/
objbind_destroy(struct objbind * ob)2166 static void objbind_destroy(struct objbind *ob)
2167 {
2168 if (!ob) {
2169 return;
2170 }
2171 if (ob->propstate_table) {
2172 propstate_hash_destroy(ob->propstate_table);
2173 ob->propstate_table = NULL;
2174 }
2175 if (ob->rowref) {
2176 gtk_tree_row_reference_free(ob->rowref);
2177 ob->rowref = NULL;
2178 }
2179 free(ob);
2180 }
2181
2182 /****************************************************************************
2183 Returns the object type of the bound object.
2184 ****************************************************************************/
objbind_get_objtype(const struct objbind * ob)2185 static enum editor_object_type objbind_get_objtype(const struct objbind *ob)
2186 {
2187 if (!ob) {
2188 return NUM_OBJTYPES;
2189 }
2190 return ob->objtype;
2191 }
2192
2193 /****************************************************************************
2194 Bind the object in the given objbind to the properties in the page.
2195 ****************************************************************************/
objbind_bind_properties(struct objbind * ob,struct property_page * pp)2196 static void objbind_bind_properties(struct objbind *ob,
2197 struct property_page *pp)
2198 {
2199 if (!ob) {
2200 return;
2201 }
2202 ob->parent_property_page = pp;
2203 }
2204
2205 /****************************************************************************
2206 Fill the packet with the bound object's current state.
2207
2208 NB: This must be updated if the packet_edit_<object> definitions change.
2209 ****************************************************************************/
objbind_pack_current_values(struct objbind * ob,union packetdata pd)2210 static void objbind_pack_current_values(struct objbind *ob,
2211 union packetdata pd)
2212 {
2213 enum editor_object_type objtype;
2214
2215 if (!ob || !pd.pointers.v_pointer1) {
2216 return;
2217 }
2218
2219 objtype = objbind_get_objtype(ob);
2220
2221 switch (objtype) {
2222 case OBJTYPE_TILE:
2223 {
2224 struct packet_edit_tile *packet = pd.tile;
2225 const struct tile *ptile = objbind_get_object(ob);
2226
2227 if (NULL == ptile) {
2228 return;
2229 }
2230
2231 packet->tile = tile_index(ptile);
2232 packet->extras = *tile_extras(ptile);
2233 /* TODO: Set more packet fields. */
2234 }
2235 return;
2236
2237 case OBJTYPE_STARTPOS:
2238 {
2239 struct packet_edit_startpos_full *packet = pd.startpos;
2240 const struct startpos *psp = objbind_get_object(ob);
2241
2242 if (NULL != psp) {
2243 startpos_pack(psp, packet);
2244 }
2245 }
2246 return;
2247
2248 case OBJTYPE_UNIT:
2249 {
2250 struct packet_edit_unit *packet = pd.unit;
2251 const struct unit *punit = objbind_get_object(ob);
2252
2253 if (NULL == punit) {
2254 return;
2255 }
2256
2257 packet->id = punit->id;
2258 packet->moves_left = punit->moves_left;
2259 packet->fuel = punit->fuel;
2260 packet->moved = punit->moved;
2261 packet->done_moving = punit->done_moving;
2262 packet->hp = punit->hp;
2263 packet->veteran = punit->veteran;
2264 /* TODO: Set more packet fields. */
2265 }
2266 return;
2267
2268 case OBJTYPE_CITY:
2269 {
2270 struct packet_edit_city *packet = pd.city;
2271 const struct city *pcity = objbind_get_object(ob);
2272 int i;
2273
2274 if (NULL == pcity) {
2275 return;
2276 }
2277
2278 packet->id = pcity->id;
2279 sz_strlcpy(packet->name, pcity->name);
2280 packet->size = city_size_get(pcity);
2281 packet->history = pcity->history;
2282 for (i = 0; i < B_LAST; i++) {
2283 packet->built[i] = pcity->built[i].turn;
2284 }
2285 packet->food_stock = pcity->food_stock;
2286 packet->shield_stock = pcity->shield_stock;
2287 /* TODO: Set more packet fields. */
2288 }
2289 return;
2290
2291 case OBJTYPE_PLAYER:
2292 {
2293 struct packet_edit_player *packet = pd.player;
2294 const struct player *pplayer = objbind_get_object(ob);
2295 const struct nation_type *pnation;
2296 const struct research *presearch;
2297
2298 if (NULL == pplayer) {
2299 return;
2300 }
2301
2302 packet->id = player_number(pplayer);
2303 sz_strlcpy(packet->name, pplayer->name);
2304 pnation = nation_of_player(pplayer);
2305 packet->nation = nation_index(pnation);
2306 presearch = research_get(pplayer);
2307 advance_index_iterate(A_FIRST, tech) {
2308 packet->inventions[tech]
2309 = TECH_KNOWN == research_invention_state(presearch, tech);
2310 } advance_index_iterate_end;
2311 packet->gold = pplayer->economic.gold;
2312 packet->government = government_index(pplayer->government);
2313 /* TODO: Set more packet fields. */
2314 }
2315 return;
2316
2317 case OBJTYPE_GAME:
2318 {
2319 struct packet_edit_game *packet = pd.game.game;
2320 const struct civ_game *pgame = objbind_get_object(ob);
2321
2322 if (NULL == pgame) {
2323 return;
2324 }
2325
2326 packet->year32 = pgame->info.year32;
2327 packet->year16 = pgame->info.year32;
2328 packet->scenario = pgame->scenario.is_scenario;
2329 sz_strlcpy(packet->scenario_name, pgame->scenario.name);
2330 sz_strlcpy(packet->scenario_authors, pgame->scenario.authors);
2331 sz_strlcpy(pd.game.desc->scenario_desc, pgame->scenario_desc.description);
2332 packet->scenario_random = pgame->scenario.save_random;
2333 packet->scenario_players = pgame->scenario.players;
2334 packet->startpos_nations = pgame->scenario.startpos_nations;
2335 packet->prevent_new_cities = pgame->scenario.prevent_new_cities;
2336 packet->lake_flooding = pgame->scenario.lake_flooding;
2337 }
2338 return;
2339
2340 case NUM_OBJTYPES:
2341 break;
2342 }
2343
2344 log_error("%s(): Unhandled object type %s (nb %d).", __FUNCTION__,
2345 objtype_get_name(objtype), objtype);
2346 }
2347
2348 /****************************************************************************
2349 Package the modified property value into the supplied packet.
2350 ****************************************************************************/
objbind_pack_modified_value(struct objbind * ob,struct objprop * op,union packetdata pd)2351 static void objbind_pack_modified_value(struct objbind *ob,
2352 struct objprop *op,
2353 union packetdata pd)
2354 {
2355 struct propval *pv;
2356 enum editor_object_type objtype;
2357 enum object_property_ids propid;
2358
2359 if (!op || !ob || !pd.pointers.v_pointer1) {
2360 return;
2361 }
2362
2363 if (NULL == objbind_get_object(ob)) {
2364 return;
2365 }
2366
2367 if (objprop_is_readonly(op) || !objbind_property_is_modified(ob, op)) {
2368 return;
2369 }
2370
2371 pv = objbind_get_modified_value(ob, op);
2372 if (!pv) {
2373 return;
2374 }
2375
2376 objtype = objbind_get_objtype(ob);
2377 propid = objprop_get_id(op);
2378
2379 switch (objtype) {
2380 case OBJTYPE_TILE:
2381 {
2382 struct packet_edit_tile *packet = pd.tile;
2383
2384 switch (propid) {
2385 case OPID_TILE_SPECIALS:
2386 extra_type_by_cause_iterate(EC_SPECIAL, pextra) {
2387 if (BV_ISSET(pv->data.v_bv_special, pextra->data.special_idx)) {
2388 BV_SET(packet->extras, pextra->data.special_idx);
2389 } else {
2390 BV_CLR(packet->extras, pextra->data.special_idx);
2391 }
2392 } extra_type_by_cause_iterate_end;
2393 return;
2394 case OPID_TILE_ROADS:
2395 extra_type_by_cause_iterate(EC_ROAD, pextra) {
2396 int ridx = road_index(extra_road_get(pextra));
2397
2398 if (BV_ISSET(pv->data.v_bv_roads, ridx)) {
2399 BV_SET(packet->extras, extra_index(pextra));
2400 } else {
2401 BV_CLR(packet->extras, extra_index(pextra));
2402 }
2403 } extra_type_by_cause_iterate_end;
2404 return;
2405 case OPID_TILE_BASES:
2406 extra_type_by_cause_iterate(EC_BASE, pextra) {
2407 int bidx = base_index(extra_base_get(pextra));
2408
2409 if (BV_ISSET(pv->data.v_bv_bases, bidx)) {
2410 BV_SET(packet->extras, extra_index(pextra));
2411 } else {
2412 BV_CLR(packet->extras, extra_index(pextra));
2413 }
2414 } extra_type_by_cause_iterate_end;
2415 return;
2416 case OPID_TILE_LABEL:
2417 sz_strlcpy(packet->label, pv->data.v_string);
2418 return;
2419 default:
2420 break;
2421 }
2422 }
2423 log_error("%s(): Unhandled request to pack value of property "
2424 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2425 propid, objprop_get_name(op), objtype_get_name(objtype));
2426 return;
2427
2428 case OBJTYPE_STARTPOS:
2429 {
2430 struct packet_edit_startpos_full *packet = pd.startpos;
2431
2432 switch (propid) {
2433 case OPID_STARTPOS_EXCLUDE:
2434 packet->exclude = pv->data.v_bool;
2435 return;
2436 case OPID_STARTPOS_NATIONS:
2437 BV_CLR_ALL(packet->nations);
2438 nation_hash_iterate(pv->data.v_nation_hash, pnation) {
2439 BV_SET(packet->nations, nation_number(pnation));
2440 } nation_hash_iterate_end;
2441 return;
2442 default:
2443 break;
2444 }
2445 }
2446 log_error("%s(): Unhandled request to pack value of property "
2447 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2448 propid, objprop_get_name(op), objtype_get_name(objtype));
2449 return;
2450
2451 case OBJTYPE_UNIT:
2452 {
2453 struct packet_edit_unit *packet = pd.unit;
2454
2455 switch (propid) {
2456 case OPID_UNIT_MOVES_LEFT:
2457 packet->moves_left = pv->data.v_int;
2458 return;
2459 case OPID_UNIT_FUEL:
2460 packet->fuel = pv->data.v_int;
2461 return;
2462 case OPID_UNIT_MOVED:
2463 packet->moved = pv->data.v_bool;
2464 return;
2465 case OPID_UNIT_DONE_MOVING:
2466 packet->done_moving = pv->data.v_bool;
2467 return;
2468 case OPID_UNIT_HP:
2469 packet->hp = pv->data.v_int;
2470 return;
2471 case OPID_UNIT_VETERAN:
2472 packet->veteran = pv->data.v_int;
2473 return;
2474 default:
2475 break;
2476 }
2477 }
2478 log_error("%s(): Unhandled request to pack value of property "
2479 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2480 propid, objprop_get_name(op), objtype_get_name(objtype));
2481 return;
2482
2483 case OBJTYPE_CITY:
2484 {
2485 struct packet_edit_city *packet = pd.city;
2486
2487 switch (propid) {
2488 case OPID_CITY_NAME:
2489 sz_strlcpy(packet->name, pv->data.v_string);
2490 return;
2491 case OPID_CITY_SIZE:
2492 packet->size = pv->data.v_int;
2493 return;
2494 case OPID_CITY_HISTORY:
2495 packet->history = pv->data.v_int;
2496 return;
2497 case OPID_CITY_FOOD_STOCK:
2498 packet->food_stock = pv->data.v_int;
2499 return;
2500 case OPID_CITY_SHIELD_STOCK:
2501 packet->shield_stock = pv->data.v_int;
2502 return;
2503 case OPID_CITY_BUILDINGS:
2504 {
2505 int i;
2506
2507 for (i = 0; i < B_LAST; i++) {
2508 packet->built[i] = pv->data.v_built[i].turn;
2509 }
2510 }
2511 return;
2512 default:
2513 break;
2514 }
2515 }
2516 log_error("%s(): Unhandled request to pack value of property "
2517 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2518 propid, objprop_get_name(op), objtype_get_name(objtype));
2519 return;
2520
2521 case OBJTYPE_PLAYER:
2522 {
2523 struct packet_edit_player *packet = pd.player;
2524
2525 switch (propid) {
2526 case OPID_PLAYER_NAME:
2527 sz_strlcpy(packet->name, pv->data.v_string);
2528 return;
2529 case OPID_PLAYER_NATION:
2530 packet->nation = nation_index(pv->data.v_nation);
2531 return;
2532 case OPID_PLAYER_GOV:
2533 packet->government = government_index(pv->data.v_gov);
2534 return;
2535 case OPID_PLAYER_INVENTIONS:
2536 advance_index_iterate(A_FIRST, tech) {
2537 packet->inventions[tech] = BV_ISSET(pv->data.v_bv_inventions, tech);
2538 } advance_index_iterate_end;
2539 return;
2540 case OPID_PLAYER_SCIENCE:
2541 packet->bulbs_researched = pv->data.v_int;
2542 return;
2543 case OPID_PLAYER_GOLD:
2544 packet->gold = pv->data.v_int;
2545 return;
2546 default:
2547 break;
2548 }
2549 }
2550 log_error("%s(): Unhandled request to pack value of property "
2551 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2552 propid, objprop_get_name(op), objtype_get_name(objtype));
2553 return;
2554
2555 case OBJTYPE_GAME:
2556 {
2557 struct packet_edit_game *packet = pd.game.game;
2558
2559 switch (propid) {
2560 case OPID_GAME_YEAR:
2561 packet->year32 = pv->data.v_int;
2562 packet->year16 = pv->data.v_int;
2563 return;
2564 case OPID_GAME_SCENARIO:
2565 packet->scenario = pv->data.v_bool;
2566 return;
2567 case OPID_GAME_SCENARIO_NAME:
2568 sz_strlcpy(packet->scenario_name, pv->data.v_const_string);
2569 return;
2570 case OPID_GAME_SCENARIO_AUTHORS:
2571 sz_strlcpy(packet->scenario_authors, pv->data.v_const_string);
2572 return;
2573 case OPID_GAME_SCENARIO_DESC:
2574 sz_strlcpy(pd.game.desc->scenario_desc, pv->data.v_const_string);
2575 return;
2576 case OPID_GAME_SCENARIO_RANDSTATE:
2577 packet->scenario_random = pv->data.v_bool;
2578 return;
2579 case OPID_GAME_SCENARIO_PLAYERS:
2580 packet->scenario_players = pv->data.v_bool;
2581 return;
2582 case OPID_GAME_STARTPOS_NATIONS:
2583 packet->startpos_nations = pv->data.v_bool;
2584 return;
2585 case OPID_GAME_PREVENT_CITIES:
2586 packet->prevent_new_cities = pv->data.v_bool;
2587 return;
2588 case OPID_GAME_LAKE_FLOODING:
2589 packet->lake_flooding = pv->data.v_bool;
2590 return;
2591 default:
2592 break;
2593 }
2594 }
2595 log_error("%s(): Unhandled request to pack value of property "
2596 "%d (%s) from object of type \"%s\".", __FUNCTION__,
2597 propid, objprop_get_name(op), objtype_get_name(objtype));
2598 return;
2599
2600 case NUM_OBJTYPES:
2601 break;
2602 }
2603
2604 log_error("%s(): Unhandled request for object type \"%s\" (nb %d).",
2605 __FUNCTION__, objtype_get_name(objtype), objtype);
2606
2607 }
2608
2609 /****************************************************************************
2610 Sets the row reference in a GtkTreeModel of this objbind.
2611 ****************************************************************************/
objbind_set_rowref(struct objbind * ob,GtkTreeRowReference * rr)2612 static void objbind_set_rowref(struct objbind *ob,
2613 GtkTreeRowReference *rr)
2614 {
2615 if (!ob) {
2616 return;
2617 }
2618 ob->rowref = rr;
2619 }
2620
2621 /****************************************************************************
2622 Returns the row reference of this objbind, or NULL if not applicable.
2623 ****************************************************************************/
objbind_get_rowref(struct objbind * ob)2624 static GtkTreeRowReference *objbind_get_rowref(struct objbind *ob)
2625 {
2626 if (!ob) {
2627 return NULL;
2628 }
2629 return ob->rowref;
2630 }
2631
2632 /****************************************************************************
2633 Returns the unique property identifier for this object property.
2634 ****************************************************************************/
objprop_get_id(const struct objprop * op)2635 static int objprop_get_id(const struct objprop *op)
2636 {
2637 if (!op) {
2638 return -1;
2639 }
2640 return op->id;
2641 }
2642
2643 /****************************************************************************
2644 Returns the GType that this object property renders as in a GtkTreeView.
2645 Returning G_TYPE_NONE indicates that this property cannot be viewed
2646 in a list.
2647
2648 NB: This must correspond to the actions in property_page_set_store_value.
2649 ****************************************************************************/
objprop_get_gtype(const struct objprop * op)2650 static GType objprop_get_gtype(const struct objprop *op)
2651 {
2652 fc_assert_ret_val(NULL != op, G_TYPE_NONE);
2653
2654 switch (op->valtype) {
2655 case VALTYPE_NONE:
2656 case VALTYPE_TILE_VISION_DATA:
2657 return G_TYPE_NONE;
2658 case VALTYPE_INT:
2659 return G_TYPE_INT;
2660 case VALTYPE_BOOL:
2661 /* We want to show it as translated string, not as untranslated G_TYPE_BOOLEAN */
2662 return G_TYPE_STRING;
2663 case VALTYPE_STRING:
2664 case VALTYPE_BUILT_ARRAY:
2665 case VALTYPE_INVENTIONS_ARRAY:
2666 case VALTYPE_BV_SPECIAL:
2667 case VALTYPE_BV_ROADS:
2668 case VALTYPE_BV_BASES:
2669 case VALTYPE_NATION_HASH:
2670 return G_TYPE_STRING;
2671 case VALTYPE_PIXBUF:
2672 case VALTYPE_NATION:
2673 case VALTYPE_GOV:
2674 return GDK_TYPE_PIXBUF;
2675 }
2676 log_error("%s(): Unhandled value type %d.", __FUNCTION__, op->valtype);
2677 return G_TYPE_NONE;
2678 }
2679
2680 /****************************************************************************
2681 Returns the value type of this property value (one of enum value_types).
2682 ****************************************************************************/
objprop_get_valtype(const struct objprop * op)2683 static enum value_types objprop_get_valtype(const struct objprop *op)
2684 {
2685 if (!op) {
2686 return VALTYPE_NONE;
2687 }
2688 return op->valtype;
2689 }
2690
2691 /****************************************************************************
2692 Returns TRUE if this object property can be viewed in a GtkTreeView.
2693 ****************************************************************************/
objprop_show_in_listview(const struct objprop * op)2694 static bool objprop_show_in_listview(const struct objprop *op)
2695 {
2696 if (!op) {
2697 return FALSE;
2698 }
2699 return op->flags & OPF_IN_LISTVIEW;
2700 }
2701
2702 /****************************************************************************
2703 Returns TRUE if this object property can create and use a property widget.
2704 ****************************************************************************/
objprop_has_widget(const struct objprop * op)2705 static bool objprop_has_widget(const struct objprop *op)
2706 {
2707 if (!op) {
2708 return FALSE;
2709 }
2710 return op->flags & OPF_HAS_WIDGET;
2711 }
2712
2713 /****************************************************************************
2714 Returns a the string corresponding to the attribute type name required
2715 for gtk_tree_view_column_new_with_attributes according to this objprop's
2716 GType value. May return NULL if it does not make sense for this
2717 object property.
2718 ****************************************************************************/
objprop_get_attribute_type_string(const struct objprop * op)2719 static const char *objprop_get_attribute_type_string(const struct objprop *op)
2720 {
2721 GType gtype;
2722
2723 if (!op) {
2724 return NULL;
2725 }
2726
2727 gtype = objprop_get_gtype(op);
2728 if (gtype == G_TYPE_INT || gtype == G_TYPE_STRING
2729 || gtype == G_TYPE_BOOLEAN) {
2730 return "text";
2731 } else if (gtype == GDK_TYPE_PIXBUF) {
2732 return "pixbuf";
2733 }
2734
2735 return NULL;
2736 }
2737
2738 /****************************************************************************
2739 Store the column id of the list store that this object property can be
2740 viewed in. This should generally only be changed set once, when the
2741 object property's associated list view is created.
2742 NB: This is the column id in the model, not the view.
2743 ****************************************************************************/
objprop_set_column_id(struct objprop * op,int col_id)2744 static void objprop_set_column_id(struct objprop *op, int col_id)
2745 {
2746 if (!op) {
2747 return;
2748 }
2749 op->column_id = col_id;
2750 }
2751
2752 /****************************************************************************
2753 Returns the column id in the list store for this object property.
2754 May return -1 if not applicable or if not yet set.
2755 NB: This is the column id in the model, not the view.
2756 ****************************************************************************/
objprop_get_column_id(const struct objprop * op)2757 static int objprop_get_column_id(const struct objprop *op)
2758 {
2759 if (!op) {
2760 return -1;
2761 }
2762 return op->column_id;
2763 }
2764
2765 /****************************************************************************
2766 Sets the view column reference for later convenience.
2767 ****************************************************************************/
objprop_set_treeview_column(struct objprop * op,GtkTreeViewColumn * col)2768 static void objprop_set_treeview_column(struct objprop *op,
2769 GtkTreeViewColumn *col)
2770 {
2771 if (!op) {
2772 return;
2773 }
2774 op->view_column = col;
2775 }
2776
2777 /****************************************************************************
2778 Returns the previously stored tree view column reference, or NULL if none
2779 exists.
2780 ****************************************************************************/
objprop_get_treeview_column(const struct objprop * op)2781 static GtkTreeViewColumn *objprop_get_treeview_column(const struct objprop *op)
2782 {
2783 if (!op) {
2784 return NULL;
2785 }
2786 return op->view_column;
2787 }
2788
2789 /****************************************************************************
2790 Returns the string name of this object property.
2791 ****************************************************************************/
objprop_get_name(const struct objprop * op)2792 static const char *objprop_get_name(const struct objprop *op)
2793 {
2794 if (!op) {
2795 return NULL;
2796 }
2797 return op->name;
2798 }
2799
2800 /****************************************************************************
2801 Return a description (translated) of the property.
2802 ****************************************************************************/
objprop_get_tooltip(const struct objprop * op)2803 static const char *objprop_get_tooltip(const struct objprop *op)
2804 {
2805 if (!op) {
2806 return NULL;
2807 }
2808 return op->tooltip;
2809 }
2810
2811 /****************************************************************************
2812 Create and return a cell renderer corresponding to this object property,
2813 suitable to be used with a tree view. May return NULL if this object
2814 property cannot exist in a list store.
2815 ****************************************************************************/
objprop_create_cell_renderer(const struct objprop * op)2816 static GtkCellRenderer *objprop_create_cell_renderer(const struct objprop *op)
2817 {
2818 GtkCellRenderer *cell = NULL;
2819 GType gtype;
2820
2821 gtype = objprop_get_gtype(op);
2822
2823 if (gtype == G_TYPE_INT || gtype == G_TYPE_STRING
2824 || gtype == G_TYPE_BOOLEAN) {
2825 cell = gtk_cell_renderer_text_new();
2826 } else if (gtype == GDK_TYPE_PIXBUF) {
2827 cell = gtk_cell_renderer_pixbuf_new();
2828 }
2829
2830 return cell;
2831 }
2832
2833 /****************************************************************************
2834 Return TRUE if the given object property can be sorted (i.e. in the list
2835 view).
2836 ****************************************************************************/
objprop_is_sortable(const struct objprop * op)2837 static bool objprop_is_sortable(const struct objprop *op)
2838 {
2839 GType gtype;
2840 if (!op) {
2841 return FALSE;
2842 }
2843 gtype = objprop_get_gtype(op);
2844 return gtype == G_TYPE_INT || gtype == G_TYPE_STRING;
2845 }
2846
2847 /****************************************************************************
2848 Return TRUE if the given object property cannot be edited (i.e. it is
2849 displayed information only).
2850 ****************************************************************************/
objprop_is_readonly(const struct objprop * op)2851 static bool objprop_is_readonly(const struct objprop *op)
2852 {
2853 if (!op) {
2854 return TRUE;
2855 }
2856 return !(op->flags & OPF_EDITABLE);
2857 }
2858
2859 /****************************************************************************
2860 Callback for entry widget 'changed' signal.
2861 ****************************************************************************/
objprop_widget_entry_changed(GtkEntry * entry,gpointer userdata)2862 static void objprop_widget_entry_changed(GtkEntry *entry, gpointer userdata)
2863 {
2864 struct objprop *op;
2865 struct property_page *pp;
2866 struct propval value = {{0,}, VALTYPE_STRING, FALSE};
2867
2868 op = userdata;
2869 pp = objprop_get_property_page(op);
2870 value.data.v_const_string = gtk_entry_get_text(entry);
2871
2872 property_page_change_value(pp, op, &value);
2873 }
2874
2875 /****************************************************************************
2876 Callback for spin button widget 'value-changed' signal.
2877 ****************************************************************************/
objprop_widget_spin_button_changed(GtkSpinButton * spin,gpointer userdata)2878 static void objprop_widget_spin_button_changed(GtkSpinButton *spin,
2879 gpointer userdata)
2880 {
2881 struct objprop *op;
2882 struct property_page *pp;
2883 struct propval value = {{0,}, VALTYPE_INT, FALSE};
2884
2885 op = userdata;
2886 pp = objprop_get_property_page(op);
2887 value.data.v_int = gtk_spin_button_get_value_as_int(spin);
2888
2889 property_page_change_value(pp, op, &value);
2890 }
2891
2892 /****************************************************************************
2893 Callback for toggle type button widget 'toggled' signal.
2894 ****************************************************************************/
objprop_widget_toggle_button_changed(GtkToggleButton * button,gpointer userdata)2895 static void objprop_widget_toggle_button_changed(GtkToggleButton *button,
2896 gpointer userdata)
2897 {
2898 struct objprop *op;
2899 struct property_page *pp;
2900 struct propval value = {{0,}, VALTYPE_BOOL, FALSE};
2901
2902 op = userdata;
2903 pp = objprop_get_property_page(op);
2904 value.data.v_bool = gtk_toggle_button_get_active(button);
2905
2906 property_page_change_value(pp, op, &value);
2907 }
2908
2909 /****************************************************************************
2910 Create the gtk widget used to edit or display this object property.
2911 ****************************************************************************/
objprop_setup_widget(struct objprop * op)2912 static void objprop_setup_widget(struct objprop *op)
2913 {
2914 GtkWidget *ebox, *hbox, *hbox2, *label, *image, *entry, *spin, *button;
2915 struct extviewer *ev = NULL;
2916 enum object_property_ids propid;
2917
2918 if (!op) {
2919 return;
2920 }
2921
2922 if (!objprop_has_widget(op)) {
2923 return;
2924 }
2925
2926 ebox = gtk_event_box_new();
2927 op->widget = ebox;
2928
2929 hbox = gtk_grid_new();
2930 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
2931
2932 gtk_container_add(GTK_CONTAINER(ebox), hbox);
2933
2934 label = gtk_label_new(objprop_get_name(op));
2935 gtk_widget_set_halign(label, GTK_ALIGN_START);
2936 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
2937 gtk_container_add(GTK_CONTAINER(hbox), label);
2938 objprop_set_child_widget(op, "name-label", label);
2939
2940 propid = objprop_get_id(op);
2941
2942 switch (propid) {
2943 case OPID_TILE_INDEX:
2944 case OPID_TILE_X:
2945 case OPID_TILE_Y:
2946 case OPID_TILE_NAT_X:
2947 case OPID_TILE_NAT_Y:
2948 case OPID_TILE_CONTINENT:
2949 case OPID_TILE_TERRAIN:
2950 case OPID_TILE_RESOURCE:
2951 case OPID_TILE_XY:
2952 case OPID_STARTPOS_XY:
2953 case OPID_UNIT_ID:
2954 case OPID_UNIT_XY:
2955 case OPID_UNIT_TYPE:
2956 case OPID_CITY_ID:
2957 case OPID_CITY_XY:
2958 case OPID_PLAYER_AGE:
2959 #ifdef DEBUG
2960 case OPID_TILE_ADDRESS:
2961 case OPID_UNIT_ADDRESS:
2962 case OPID_CITY_ADDRESS:
2963 case OPID_PLAYER_ADDRESS:
2964 #endif /* DEBUG */
2965 label = gtk_label_new(NULL);
2966 gtk_widget_set_hexpand(label, TRUE);
2967 gtk_widget_set_halign(label, GTK_ALIGN_START);
2968 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
2969 gtk_container_add(GTK_CONTAINER(hbox), label);
2970 objprop_set_child_widget(op, "value-label", label);
2971 return;
2972
2973 case OPID_TILE_IMAGE:
2974 case OPID_STARTPOS_IMAGE:
2975 case OPID_UNIT_IMAGE:
2976 case OPID_CITY_IMAGE:
2977 image = gtk_image_new();
2978 gtk_widget_set_hexpand(image, TRUE);
2979 gtk_widget_set_halign(image, GTK_ALIGN_START);
2980 gtk_widget_set_valign(image, GTK_ALIGN_CENTER);
2981 gtk_container_add(GTK_CONTAINER(hbox), image);
2982 objprop_set_child_widget(op, "image", image);
2983 return;
2984
2985 case OPID_CITY_NAME:
2986 case OPID_PLAYER_NAME:
2987 case OPID_GAME_SCENARIO_NAME:
2988 case OPID_TILE_LABEL:
2989 entry = gtk_entry_new();
2990 gtk_widget_set_hexpand(entry, TRUE);
2991 gtk_widget_set_halign(entry, GTK_ALIGN_END);
2992 gtk_entry_set_width_chars(GTK_ENTRY(entry), 8);
2993 g_signal_connect(entry, "changed",
2994 G_CALLBACK(objprop_widget_entry_changed), op);
2995 gtk_container_add(GTK_CONTAINER(hbox), entry);
2996 objprop_set_child_widget(op, "entry", entry);
2997 return;
2998
2999 case OPID_UNIT_MOVES_LEFT:
3000 case OPID_CITY_SIZE:
3001 case OPID_CITY_HISTORY:
3002 case OPID_CITY_SHIELD_STOCK:
3003 case OPID_PLAYER_SCIENCE:
3004 case OPID_PLAYER_GOLD:
3005 case OPID_GAME_YEAR:
3006 spin = gtk_spin_button_new_with_range(0.0, 100.0, 1.0);
3007 gtk_widget_set_hexpand(spin, TRUE);
3008 gtk_widget_set_halign(spin, GTK_ALIGN_END);
3009 g_signal_connect(spin, "value-changed",
3010 G_CALLBACK(objprop_widget_spin_button_changed), op);
3011 gtk_container_add(GTK_CONTAINER(hbox), spin);
3012 objprop_set_child_widget(op, "spin", spin);
3013 return;
3014
3015 case OPID_UNIT_FUEL:
3016 case OPID_UNIT_HP:
3017 case OPID_UNIT_VETERAN:
3018 case OPID_CITY_FOOD_STOCK:
3019 hbox2 = gtk_grid_new();
3020 gtk_widget_set_hexpand(hbox2, TRUE);
3021 gtk_widget_set_halign(hbox2, GTK_ALIGN_END);
3022 gtk_grid_set_column_spacing(GTK_GRID(hbox2), 4);
3023 gtk_container_add(GTK_CONTAINER(hbox), hbox2);
3024 spin = gtk_spin_button_new_with_range(0.0, 100.0, 1.0);
3025 g_signal_connect(spin, "value-changed",
3026 G_CALLBACK(objprop_widget_spin_button_changed), op);
3027 gtk_container_add(GTK_CONTAINER(hbox2), spin);
3028 objprop_set_child_widget(op, "spin", spin);
3029 label = gtk_label_new(NULL);
3030 gtk_widget_set_halign(label, GTK_ALIGN_START);
3031 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
3032 gtk_container_add(GTK_CONTAINER(hbox2), label);
3033 objprop_set_child_widget(op, "max-value-label", label);
3034 return;
3035
3036 case OPID_TILE_SPECIALS:
3037 case OPID_TILE_ROADS:
3038 case OPID_TILE_BASES:
3039 case OPID_TILE_VISION:
3040 case OPID_STARTPOS_NATIONS:
3041 case OPID_CITY_BUILDINGS:
3042 case OPID_PLAYER_NATION:
3043 case OPID_PLAYER_GOV:
3044 case OPID_PLAYER_INVENTIONS:
3045 case OPID_GAME_SCENARIO_AUTHORS:
3046 case OPID_GAME_SCENARIO_DESC:
3047 ev = extviewer_new(op);
3048 objprop_set_extviewer(op, ev);
3049 gtk_widget_set_hexpand(extviewer_get_panel_widget(ev), TRUE);
3050 gtk_widget_set_halign(extviewer_get_panel_widget(ev), GTK_ALIGN_END);
3051 gtk_container_add(GTK_CONTAINER(hbox), extviewer_get_panel_widget(ev));
3052 property_page_add_extviewer(objprop_get_property_page(op), ev);
3053 return;
3054
3055 case OPID_STARTPOS_EXCLUDE:
3056 case OPID_UNIT_MOVED:
3057 case OPID_UNIT_DONE_MOVING:
3058 case OPID_GAME_SCENARIO:
3059 case OPID_GAME_SCENARIO_RANDSTATE:
3060 case OPID_GAME_SCENARIO_PLAYERS:
3061 case OPID_GAME_STARTPOS_NATIONS:
3062 case OPID_GAME_PREVENT_CITIES:
3063 case OPID_GAME_LAKE_FLOODING:
3064 button = gtk_check_button_new();
3065 gtk_widget_set_hexpand(button, TRUE);
3066 gtk_widget_set_halign(button, GTK_ALIGN_END);
3067 g_signal_connect(button, "toggled",
3068 G_CALLBACK(objprop_widget_toggle_button_changed), op);
3069 gtk_container_add(GTK_CONTAINER(hbox), button);
3070 objprop_set_child_widget(op, "checkbutton", button);
3071 return;
3072 }
3073
3074 log_error("%s(): Unhandled request to create widget for property %d (%s).",
3075 __FUNCTION__, propid, objprop_get_name(op));
3076 }
3077
3078 /****************************************************************************
3079 Refresh the display/edit widget corresponding to this object property
3080 according to the value of the bound object. If a stored modified value
3081 exists, then check it against the object's current value and remove it
3082 if they are equal.
3083
3084 If 'ob' is NULL, then clear the widget.
3085 ****************************************************************************/
objprop_refresh_widget(struct objprop * op,struct objbind * ob)3086 static void objprop_refresh_widget(struct objprop *op,
3087 struct objbind *ob)
3088 {
3089 GtkWidget *w, *label, *image, *entry, *spin, *button;
3090 struct extviewer *ev;
3091 struct propval *pv;
3092 bool modified;
3093 enum object_property_ids propid;
3094 double min, max, step, big_step;
3095 char buf[256];
3096
3097 if (!op || !objprop_has_widget(op)) {
3098 return;
3099 }
3100
3101 w = objprop_get_widget(op);
3102 if (!w) {
3103 return;
3104 }
3105
3106 propid = objprop_get_id(op);
3107
3108 /* NB: We must take care to propval_free the return value of
3109 * objbind_get_value_from_object, since it always makes a
3110 * copy, but to NOT free the result of objbind_get_modified_value
3111 * since it returns its own stored value. */
3112 pv = objbind_get_value_from_object(ob, op);
3113 modified = objbind_property_is_modified(ob, op);
3114
3115 if (pv && modified) {
3116 struct propval *pv_mod;
3117
3118 pv_mod = objbind_get_modified_value(ob, op);
3119 if (pv_mod) {
3120 if (propval_equal(pv, pv_mod)) {
3121 objbind_clear_modified_value(ob, op);
3122 modified = FALSE;
3123 } else {
3124 propval_free(pv);
3125 pv = pv_mod;
3126 modified = TRUE;
3127 }
3128 } else {
3129 modified = FALSE;
3130 }
3131 }
3132
3133 switch (propid) {
3134 case OPID_TILE_IMAGE:
3135 case OPID_STARTPOS_IMAGE:
3136 case OPID_UNIT_IMAGE:
3137 case OPID_CITY_IMAGE:
3138 image = objprop_get_child_widget(op, "image");
3139 if (pv) {
3140 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pv->data.v_pixbuf);
3141 } else {
3142 gtk_image_set_from_pixbuf(GTK_IMAGE(image), NULL);
3143 }
3144 break;
3145
3146 case OPID_TILE_XY:
3147 case OPID_TILE_TERRAIN:
3148 case OPID_TILE_RESOURCE:
3149 case OPID_STARTPOS_XY:
3150 case OPID_UNIT_XY:
3151 case OPID_UNIT_TYPE:
3152 case OPID_CITY_XY:
3153 #ifdef DEBUG
3154 case OPID_TILE_ADDRESS:
3155 case OPID_UNIT_ADDRESS:
3156 case OPID_CITY_ADDRESS:
3157 case OPID_PLAYER_ADDRESS:
3158 #endif /* DEBUG */
3159 label = objprop_get_child_widget(op, "value-label");
3160 if (pv) {
3161 gtk_label_set_text(GTK_LABEL(label), pv->data.v_string);
3162 } else {
3163 gtk_label_set_text(GTK_LABEL(label), NULL);
3164 }
3165 break;
3166
3167 case OPID_TILE_INDEX:
3168 case OPID_TILE_X:
3169 case OPID_TILE_Y:
3170 case OPID_TILE_NAT_X:
3171 case OPID_TILE_NAT_Y:
3172 case OPID_TILE_CONTINENT:
3173 case OPID_UNIT_ID:
3174 case OPID_CITY_ID:
3175 case OPID_PLAYER_AGE:
3176 label = objprop_get_child_widget(op, "value-label");
3177 if (pv) {
3178 char agebuf[16];
3179
3180 fc_snprintf(agebuf, sizeof(agebuf), "%d", pv->data.v_int);
3181 gtk_label_set_text(GTK_LABEL(label), agebuf);
3182 } else {
3183 gtk_label_set_text(GTK_LABEL(label), NULL);
3184 }
3185 break;
3186
3187 case OPID_CITY_NAME:
3188 case OPID_PLAYER_NAME:
3189 case OPID_GAME_SCENARIO_NAME:
3190 case OPID_TILE_LABEL:
3191 entry = objprop_get_child_widget(op, "entry");
3192 if (pv) {
3193 gtk_entry_set_text(GTK_ENTRY(entry), pv->data.v_string);
3194 } else {
3195 gtk_entry_set_text(GTK_ENTRY(entry), "");
3196 }
3197 gtk_widget_set_sensitive(entry, pv != NULL);
3198 break;
3199
3200 case OPID_UNIT_MOVES_LEFT:
3201 case OPID_CITY_SIZE:
3202 case OPID_CITY_HISTORY:
3203 case OPID_CITY_SHIELD_STOCK:
3204 case OPID_PLAYER_SCIENCE:
3205 case OPID_PLAYER_GOLD:
3206 case OPID_GAME_YEAR:
3207 spin = objprop_get_child_widget(op, "spin");
3208 if (pv) {
3209 disable_gobject_callback(G_OBJECT(spin),
3210 G_CALLBACK(objprop_widget_spin_button_changed));
3211 if (objbind_get_allowed_value_span(ob, op, &min, &max,
3212 &step, &big_step)) {
3213 gtk_spin_button_set_range(GTK_SPIN_BUTTON(spin), min, max);
3214 gtk_spin_button_set_increments(GTK_SPIN_BUTTON(spin),
3215 step, big_step);
3216 }
3217 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), pv->data.v_int);
3218 enable_gobject_callback(G_OBJECT(spin),
3219 G_CALLBACK(objprop_widget_spin_button_changed));
3220 }
3221 gtk_widget_set_sensitive(spin, pv != NULL);
3222 break;
3223
3224 case OPID_UNIT_FUEL:
3225 case OPID_UNIT_HP:
3226 case OPID_UNIT_VETERAN:
3227 case OPID_CITY_FOOD_STOCK:
3228 spin = objprop_get_child_widget(op, "spin");
3229 label = objprop_get_child_widget(op, "max-value-label");
3230 if (pv) {
3231 disable_gobject_callback(G_OBJECT(spin),
3232 G_CALLBACK(objprop_widget_spin_button_changed));
3233 if (objbind_get_allowed_value_span(ob, op, &min, &max,
3234 &step, &big_step)) {
3235 gtk_spin_button_set_range(GTK_SPIN_BUTTON(spin), min, max);
3236 gtk_spin_button_set_increments(GTK_SPIN_BUTTON(spin),
3237 step, big_step);
3238 fc_snprintf(buf, sizeof(buf), "/%d", (int) max);
3239 gtk_label_set_text(GTK_LABEL(label), buf);
3240 } else {
3241 gtk_label_set_text(GTK_LABEL(label), NULL);
3242 }
3243 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), pv->data.v_int);
3244 enable_gobject_callback(G_OBJECT(spin),
3245 G_CALLBACK(objprop_widget_spin_button_changed));
3246 } else {
3247 gtk_label_set_text(GTK_LABEL(label), NULL);
3248 }
3249 gtk_widget_set_sensitive(spin, pv != NULL);
3250 break;
3251
3252 case OPID_TILE_SPECIALS:
3253 case OPID_TILE_ROADS:
3254 case OPID_TILE_BASES:
3255 case OPID_TILE_VISION:
3256 case OPID_STARTPOS_NATIONS:
3257 case OPID_CITY_BUILDINGS:
3258 case OPID_PLAYER_NATION:
3259 case OPID_PLAYER_GOV:
3260 case OPID_PLAYER_INVENTIONS:
3261 case OPID_GAME_SCENARIO_AUTHORS:
3262 case OPID_GAME_SCENARIO_DESC:
3263 ev = objprop_get_extviewer(op);
3264 if (pv) {
3265 extviewer_refresh_widgets(ev, pv);
3266 } else {
3267 extviewer_clear_widgets(ev);
3268 }
3269 break;
3270
3271 case OPID_STARTPOS_EXCLUDE:
3272 case OPID_UNIT_MOVED:
3273 case OPID_UNIT_DONE_MOVING:
3274 case OPID_GAME_SCENARIO:
3275 case OPID_GAME_SCENARIO_RANDSTATE:
3276 case OPID_GAME_SCENARIO_PLAYERS:
3277 case OPID_GAME_STARTPOS_NATIONS:
3278 case OPID_GAME_PREVENT_CITIES:
3279 case OPID_GAME_LAKE_FLOODING:
3280 button = objprop_get_child_widget(op, "checkbutton");
3281 disable_gobject_callback(G_OBJECT(button),
3282 G_CALLBACK(objprop_widget_toggle_button_changed));
3283 if (pv) {
3284 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
3285 pv->data.v_bool);
3286 } else {
3287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
3288 }
3289 enable_gobject_callback(G_OBJECT(button),
3290 G_CALLBACK(objprop_widget_toggle_button_changed));
3291 gtk_widget_set_sensitive(button, pv != NULL);
3292 break;
3293 }
3294
3295 if (!modified) {
3296 propval_free(pv);
3297 }
3298
3299 label = objprop_get_child_widget(op, "name-label");
3300 if (label) {
3301 const char *name = objprop_get_name(op);
3302 if (modified) {
3303 char namebuf[128];
3304
3305 fc_snprintf(namebuf, sizeof(namebuf),
3306 "<span foreground=\"red\">%s</span>", name);
3307 gtk_label_set_markup(GTK_LABEL(label), namebuf);
3308 } else {
3309 gtk_label_set_text(GTK_LABEL(label), name);
3310 }
3311 }
3312 }
3313
3314 /****************************************************************************
3315 Returns the gtk widget used to display or edit this property, or NULL
3316 if not applicable.
3317 ****************************************************************************/
objprop_get_widget(struct objprop * op)3318 static GtkWidget *objprop_get_widget(struct objprop *op)
3319 {
3320 if (!op) {
3321 return NULL;
3322 }
3323 if (!op->widget) {
3324 objprop_setup_widget(op);
3325 }
3326 return op->widget;
3327 }
3328
3329 /****************************************************************************
3330 Stores the given widget under the given name. This function will have no
3331 effect if objprop_get_widget does not return a valid GtkWidget instance.
3332 ****************************************************************************/
objprop_set_child_widget(struct objprop * op,const char * widget_name,GtkWidget * widget)3333 static void objprop_set_child_widget(struct objprop *op,
3334 const char *widget_name,
3335 GtkWidget *widget)
3336 {
3337 GtkWidget *w;
3338
3339 if (!op || !widget_name || !widget) {
3340 return;
3341 }
3342
3343 w = objprop_get_widget(op);
3344 if (!w) {
3345 log_error("Cannot store child widget %p under name "
3346 "\"%s\" using objprop_set_child_widget for object "
3347 "property %d (%s) because objprop_get_widget does "
3348 "not return a valid widget.",
3349 widget, widget_name, objprop_get_id(op), objprop_get_name(op));
3350 return;
3351 }
3352
3353 g_object_set_data(G_OBJECT(w), widget_name, widget);
3354 }
3355
3356 /****************************************************************************
3357 Retrieves the widget stored under the given name, or returns NULL and
3358 logs an error message if not found.
3359 ****************************************************************************/
objprop_get_child_widget(struct objprop * op,const char * widget_name)3360 static GtkWidget *objprop_get_child_widget(struct objprop *op,
3361 const char *widget_name)
3362 {
3363 GtkWidget *w, *child;
3364
3365 if (!op || !widget_name) {
3366 return NULL;
3367 }
3368
3369 w = objprop_get_widget(op);
3370 if (!w) {
3371 log_error("Cannot retrieve child widget under name "
3372 "\"%s\" using objprop_get_child_widget for object "
3373 "property %d (%s) because objprop_get_widget does "
3374 "not return a valid widget.",
3375 widget_name, objprop_get_id(op), objprop_get_name(op));
3376 return NULL;
3377 }
3378
3379 child = g_object_get_data(G_OBJECT(w), widget_name);
3380 if (!child) {
3381 log_error("Child widget \"%s\" not found for object "
3382 "property %d (%s) via objprop_get_child_widget.",
3383 widget_name, objprop_get_id(op), objprop_get_name(op));
3384 return NULL;
3385 }
3386
3387 return child;
3388 }
3389
3390 /****************************************************************************
3391 Store the extviewer struct for later retrieval.
3392 ****************************************************************************/
objprop_set_extviewer(struct objprop * op,struct extviewer * ev)3393 static void objprop_set_extviewer(struct objprop *op,
3394 struct extviewer *ev)
3395 {
3396 if (!op) {
3397 return;
3398 }
3399 op->extviewer = ev;
3400 }
3401
3402 /****************************************************************************
3403 Return the stored extviewer, or NULL if none exists.
3404 ****************************************************************************/
objprop_get_extviewer(struct objprop * op)3405 static struct extviewer *objprop_get_extviewer(struct objprop *op)
3406 {
3407 if (!op) {
3408 return NULL;
3409 }
3410 return op->extviewer;
3411 }
3412
3413 /****************************************************************************
3414 Get the property page in which this property is installed.
3415 ****************************************************************************/
objprop_get_property_page(const struct objprop * op)3416 static struct property_page *objprop_get_property_page(const struct objprop *op)
3417 {
3418 if (!op) {
3419 return NULL;
3420 }
3421 return op->parent_page;
3422 }
3423
3424 /****************************************************************************
3425 Create a new object property.
3426 ****************************************************************************/
objprop_new(int id,const char * name,const char * tooltip,enum object_property_flags flags,enum value_types valtype,struct property_page * parent)3427 static struct objprop *objprop_new(int id,
3428 const char *name,
3429 const char *tooltip,
3430 enum object_property_flags flags,
3431 enum value_types valtype,
3432 struct property_page *parent)
3433 {
3434 struct objprop *op;
3435
3436 op = fc_calloc(1, sizeof(*op));
3437 op->id = id;
3438 op->name = name;
3439 op->tooltip = tooltip;
3440 op->flags = flags;
3441 op->valtype = valtype;
3442 op->column_id = -1;
3443 op->parent_page = parent;
3444
3445 return op;
3446 }
3447
3448 /****************************************************************************
3449 Create "extended property viewer". This is used for viewing/editing
3450 complex property values (e.g. arrays, bitvectors, etc.).
3451 ****************************************************************************/
extviewer_new(struct objprop * op)3452 static struct extviewer *extviewer_new(struct objprop *op)
3453 {
3454 struct extviewer *ev;
3455 GtkWidget *hbox, *vbox, *label, *button, *scrollwin, *image;
3456 GtkWidget *view = NULL;
3457 GtkTreeSelection *sel;
3458 GtkListStore *store = NULL;
3459 GtkTextBuffer *textbuf = NULL;
3460 GType *gtypes;
3461 enum object_property_ids propid;
3462 int num_cols;
3463
3464 if (!op) {
3465 return NULL;
3466 }
3467
3468 ev = fc_calloc(1, sizeof(*ev));
3469 ev->objprop = op;
3470
3471 propid = objprop_get_id(op);
3472
3473
3474 /* Create the panel widget. */
3475
3476 switch (propid) {
3477 case OPID_TILE_SPECIALS:
3478 case OPID_TILE_ROADS:
3479 case OPID_TILE_BASES:
3480 case OPID_STARTPOS_NATIONS:
3481 case OPID_CITY_BUILDINGS:
3482 case OPID_PLAYER_INVENTIONS:
3483 case OPID_GAME_SCENARIO_AUTHORS:
3484 case OPID_GAME_SCENARIO_DESC:
3485 hbox = gtk_grid_new();
3486 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
3487 ev->panel_widget = hbox;
3488
3489 label = gtk_label_new(NULL);
3490 gtk_widget_set_halign(label, GTK_ALIGN_START);
3491 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
3492 gtk_container_add(GTK_CONTAINER(hbox), label);
3493 ev->panel_label = label;
3494 break;
3495
3496 case OPID_PLAYER_NATION:
3497 case OPID_PLAYER_GOV:
3498 vbox = gtk_grid_new();
3499 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
3500 GTK_ORIENTATION_VERTICAL);
3501 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
3502 ev->panel_widget = vbox;
3503
3504 label = gtk_label_new(NULL);
3505 gtk_widget_set_halign(label, GTK_ALIGN_START);
3506 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
3507 gtk_container_add(GTK_CONTAINER(vbox), label);
3508 ev->panel_label = label;
3509
3510 hbox = gtk_grid_new();
3511 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
3512 gtk_container_add(GTK_CONTAINER(vbox), hbox);
3513
3514 image = gtk_image_new();
3515 gtk_widget_set_halign(image, GTK_ALIGN_START);
3516 gtk_widget_set_valign(image, GTK_ALIGN_CENTER);
3517 gtk_container_add(GTK_CONTAINER(hbox), image);
3518 ev->panel_image = image;
3519 break;
3520
3521 case OPID_TILE_VISION:
3522 hbox = gtk_grid_new();
3523 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
3524 ev->panel_widget = hbox;
3525 break;
3526
3527 default:
3528 log_error("Unhandled request to create panel widget "
3529 "for property %d (%s) in extviewer_new().",
3530 propid, objprop_get_name(op));
3531 hbox = gtk_grid_new();
3532 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
3533 ev->panel_widget = hbox;
3534 break;
3535 }
3536
3537 if (objprop_is_readonly(op)) {
3538 button = gtk_button_new_with_label(Q_("?verb:View"));
3539 } else {
3540 button = gtk_button_new_with_label(_("Edit"));
3541 }
3542 g_signal_connect(button, "clicked",
3543 G_CALLBACK(extviewer_panel_button_clicked), ev);
3544 gtk_container_add(GTK_CONTAINER(hbox), button);
3545 ev->panel_button = button;
3546
3547
3548 /* Create the data store. */
3549
3550 switch (propid) {
3551 case OPID_TILE_SPECIALS:
3552 case OPID_TILE_ROADS:
3553 case OPID_TILE_BASES:
3554 case OPID_PLAYER_INVENTIONS:
3555 store = gtk_list_store_new(3, G_TYPE_BOOLEAN, G_TYPE_INT,
3556 G_TYPE_STRING);
3557 break;
3558 case OPID_TILE_VISION:
3559 num_cols = 3 + 1 + V_COUNT;
3560 gtypes = fc_malloc(num_cols * sizeof(GType));
3561 gtypes[0] = G_TYPE_INT; /* player number */
3562 gtypes[1] = GDK_TYPE_PIXBUF; /* player flag */
3563 gtypes[2] = G_TYPE_STRING; /* player name */
3564 gtypes[3] = G_TYPE_BOOLEAN; /* tile_known */
3565 vision_layer_iterate(v) {
3566 gtypes[4 + v] = G_TYPE_BOOLEAN; /* tile_seen[v] */
3567 } vision_layer_iterate_end;
3568 store = gtk_list_store_newv(num_cols, gtypes);
3569 free(gtypes);
3570 break;
3571 case OPID_CITY_BUILDINGS:
3572 store = gtk_list_store_new(4, G_TYPE_BOOLEAN, G_TYPE_INT,
3573 G_TYPE_STRING, G_TYPE_STRING);
3574 break;
3575 case OPID_STARTPOS_NATIONS:
3576 case OPID_PLAYER_NATION:
3577 case OPID_PLAYER_GOV:
3578 store = gtk_list_store_new(4, G_TYPE_BOOLEAN, G_TYPE_INT,
3579 GDK_TYPE_PIXBUF, G_TYPE_STRING);
3580 break;
3581 case OPID_GAME_SCENARIO_AUTHORS:
3582 case OPID_GAME_SCENARIO_DESC:
3583 textbuf = gtk_text_buffer_new(NULL);
3584 break;
3585 default:
3586 log_error("Unhandled request to create data store "
3587 "for property %d (%s) in extviewer_new().",
3588 propid, objprop_get_name(op));
3589 break;
3590 }
3591
3592 ev->store = store;
3593 ev->textbuf = textbuf;
3594
3595 /* Create the view widget. */
3596
3597 vbox = gtk_grid_new();
3598 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
3599 GTK_ORIENTATION_VERTICAL);
3600 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
3601 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
3602 ev->view_widget = vbox;
3603
3604 label = gtk_label_new(objprop_get_name(op));
3605 gtk_widget_set_halign(label, GTK_ALIGN_START);
3606 gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
3607 gtk_container_add(GTK_CONTAINER(vbox), label);
3608 ev->view_label = label;
3609
3610 if (store || textbuf) {
3611 scrollwin = gtk_scrolled_window_new(NULL, NULL);
3612 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
3613 GTK_SHADOW_ETCHED_IN);
3614 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
3615 GTK_POLICY_AUTOMATIC,
3616 GTK_POLICY_AUTOMATIC);
3617 gtk_container_add(GTK_CONTAINER(vbox), scrollwin);
3618
3619 if (store) {
3620 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3621 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
3622 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
3623 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
3624 } else {
3625 const bool editable = !objprop_is_readonly(op);
3626 view = gtk_text_view_new_with_buffer(textbuf);
3627 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), editable);
3628 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), editable);
3629 }
3630 gtk_widget_set_hexpand(view, TRUE);
3631 gtk_widget_set_vexpand(view, TRUE);
3632
3633 gtk_container_add(GTK_CONTAINER(scrollwin), view);
3634 }
3635
3636 switch (propid) {
3637
3638 case OPID_TILE_SPECIALS:
3639 case OPID_TILE_ROADS:
3640 case OPID_TILE_BASES:
3641 /* TRANS: As in "this tile special is present". */
3642 add_column(view, 0, _("Present"), G_TYPE_BOOLEAN, TRUE, FALSE,
3643 G_CALLBACK(extviewer_view_cell_toggled), ev);
3644 add_column(view, 1, _("ID"), G_TYPE_INT,
3645 FALSE, FALSE, NULL, NULL);
3646 add_column(view, 2, _("Name"), G_TYPE_STRING,
3647 FALSE, FALSE, NULL, NULL);
3648 break;
3649
3650 case OPID_TILE_VISION:
3651 add_column(view, 0, _("ID"), G_TYPE_INT,
3652 FALSE, FALSE, NULL, NULL);
3653 add_column(view, 1, _("Nation"), GDK_TYPE_PIXBUF,
3654 FALSE, FALSE, NULL, NULL);
3655 add_column(view, 2, _("Name"), G_TYPE_STRING,
3656 FALSE, FALSE, NULL, NULL);
3657 add_column(view, 3, _("Known"), G_TYPE_BOOLEAN,
3658 FALSE, FALSE, NULL, NULL);
3659 vision_layer_iterate(v) {
3660 add_column(view, 4 + v, vision_layer_get_name(v),
3661 G_TYPE_BOOLEAN, FALSE, FALSE, NULL, NULL);
3662 } vision_layer_iterate_end;
3663 break;
3664
3665 case OPID_CITY_BUILDINGS:
3666 /* TRANS: As in "this building is present". */
3667 add_column(view, 0, _("Present"), G_TYPE_BOOLEAN, TRUE, FALSE,
3668 G_CALLBACK(extviewer_view_cell_toggled), ev);
3669 add_column(view, 1, _("ID"), G_TYPE_INT,
3670 FALSE, FALSE, NULL, NULL);
3671 add_column(view, 2, _("Name"), G_TYPE_STRING,
3672 FALSE, FALSE, NULL, NULL);
3673 /* TRANS: As in "the turn when this building was built". */
3674 add_column(view, 3, _("Turn Built"), G_TYPE_STRING,
3675 FALSE, FALSE, NULL, NULL);
3676 break;
3677
3678 case OPID_STARTPOS_NATIONS:
3679 /* TRANS: As in "the player has set this nation". */
3680 add_column(view, 0, _("Set"), G_TYPE_BOOLEAN, TRUE, FALSE,
3681 G_CALLBACK(extviewer_view_cell_toggled), ev);
3682 add_column(view, 1, _("ID"), G_TYPE_INT,
3683 FALSE, FALSE, NULL, NULL);
3684 add_column(view, 2, _("Flag"), GDK_TYPE_PIXBUF,
3685 FALSE, FALSE, NULL, NULL);
3686 add_column(view, 3, _("Name"), G_TYPE_STRING,
3687 FALSE, FALSE, NULL, NULL);
3688 break;
3689
3690 case OPID_PLAYER_NATION:
3691 case OPID_PLAYER_GOV:
3692 /* TRANS: As in "the player has set this nation". */
3693 add_column(view, 0, _("Set"), G_TYPE_BOOLEAN, TRUE, TRUE,
3694 G_CALLBACK(extviewer_view_cell_toggled), ev);
3695 add_column(view, 1, _("ID"), G_TYPE_INT,
3696 FALSE, FALSE, NULL, NULL);
3697 add_column(view, 2,
3698 propid == OPID_PLAYER_GOV ? _("Icon") : _("Flag"),
3699 GDK_TYPE_PIXBUF,
3700 FALSE, FALSE, NULL, NULL);
3701 add_column(view, 3, _("Name"), G_TYPE_STRING,
3702 FALSE, FALSE, NULL, NULL);
3703 break;
3704
3705 case OPID_PLAYER_INVENTIONS:
3706 /* TRANS: As in "this invention is known". */
3707 add_column(view, 0, _("Known"), G_TYPE_BOOLEAN, TRUE, FALSE,
3708 G_CALLBACK(extviewer_view_cell_toggled), ev);
3709 add_column(view, 1, _("ID"), G_TYPE_INT,
3710 FALSE, FALSE, NULL, NULL);
3711 add_column(view, 2, _("Name"), G_TYPE_STRING,
3712 FALSE, FALSE, NULL, NULL);
3713 break;
3714
3715 case OPID_GAME_SCENARIO_AUTHORS:
3716 case OPID_GAME_SCENARIO_DESC:
3717 g_signal_connect(textbuf, "changed",
3718 G_CALLBACK(extviewer_textbuf_changed), ev);
3719 break;
3720
3721 default:
3722 log_error("Unhandled request to configure view widget "
3723 "for property %d (%s) in extviewer_new().",
3724 propid, objprop_get_name(op));
3725 break;
3726 }
3727
3728 gtk_widget_show_all(ev->panel_widget);
3729 gtk_widget_show_all(ev->view_widget);
3730
3731 return ev;
3732 }
3733
3734 /****************************************************************************
3735 Returns the object property that is displayed by this extviewer.
3736 ****************************************************************************/
extviewer_get_objprop(struct extviewer * ev)3737 static struct objprop *extviewer_get_objprop(struct extviewer *ev)
3738 {
3739 if (!ev) {
3740 return NULL;
3741 }
3742 return ev->objprop;
3743 }
3744
3745 /****************************************************************************
3746 Returns the "panel widget" for this extviewer, i.e. the widget the
3747 is to be placed into the properties panel.
3748 ****************************************************************************/
extviewer_get_panel_widget(struct extviewer * ev)3749 static GtkWidget *extviewer_get_panel_widget(struct extviewer *ev)
3750 {
3751 if (!ev) {
3752 return NULL;
3753 }
3754 return ev->panel_widget;
3755 }
3756
3757 /****************************************************************************
3758 Returns the "view widget" for this extviewer, i.e. the widget the
3759 is to be used for viewing/editing a complex property.
3760 ****************************************************************************/
extviewer_get_view_widget(struct extviewer * ev)3761 static GtkWidget *extviewer_get_view_widget(struct extviewer *ev)
3762 {
3763 if (!ev) {
3764 return NULL;
3765 }
3766 return ev->view_widget;
3767 }
3768
3769 /****************************************************************************
3770 Set the widgets in the extended property viewer to display the given value.
3771 ****************************************************************************/
extviewer_refresh_widgets(struct extviewer * ev,struct propval * pv)3772 static void extviewer_refresh_widgets(struct extviewer *ev,
3773 struct propval *pv)
3774 {
3775 struct objprop *op;
3776 enum object_property_ids propid;
3777 int id, turn_built;
3778 bool present, all;
3779 const char *name;
3780 GdkPixbuf *pixbuf;
3781 GtkListStore *store;
3782 GtkTextBuffer *textbuf;
3783 GtkTreeIter iter;
3784 gchar *buf;
3785
3786 if (!ev) {
3787 return;
3788 }
3789
3790 op = extviewer_get_objprop(ev);
3791 propid = objprop_get_id(op);
3792
3793 if (propval_equal(pv, ev->pv_cached)) {
3794 return;
3795 }
3796 propval_free(ev->pv_cached);
3797 ev->pv_cached = propval_copy(pv);
3798 store = ev->store;
3799 textbuf = ev->textbuf;
3800
3801
3802 /* NB: Remember to have -1 as the last argument to
3803 * gtk_list_store_set() and to use the correct column
3804 * number when inserting data. :) */
3805 switch (propid) {
3806
3807 case OPID_TILE_SPECIALS:
3808 gtk_list_store_clear(store);
3809 extra_type_by_cause_iterate(EC_SPECIAL, spe) {
3810 id = spe->data.special_idx;
3811 name = extra_name_translation(spe);
3812 present = BV_ISSET(pv->data.v_bv_special, id);
3813 gtk_list_store_append(store, &iter);
3814 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3815 } extra_type_by_cause_iterate_end;
3816 buf = propval_as_string(pv);
3817 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3818 g_free(buf);
3819 break;
3820
3821 case OPID_TILE_ROADS:
3822 gtk_list_store_clear(store);
3823 extra_type_by_cause_iterate(EC_ROAD, pextra) {
3824 struct road_type *proad = extra_road_get(pextra);
3825
3826 id = road_number(proad);
3827 name = extra_name_translation(pextra);
3828 present = BV_ISSET(pv->data.v_bv_roads, id);
3829 gtk_list_store_append(store, &iter);
3830 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3831 } extra_type_by_cause_iterate_end;
3832 buf = propval_as_string(pv);
3833 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3834 g_free(buf);
3835 break;
3836
3837 case OPID_TILE_BASES:
3838 gtk_list_store_clear(store);
3839 extra_type_by_cause_iterate(EC_BASE, pextra) {
3840 struct base_type *pbase = extra_base_get(pextra);
3841
3842 id = base_number(pbase);
3843 name = extra_name_translation(pextra);
3844 present = BV_ISSET(pv->data.v_bv_bases, id);
3845 gtk_list_store_append(store, &iter);
3846 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3847 } extra_type_by_cause_iterate_end;
3848 buf = propval_as_string(pv);
3849 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3850 g_free(buf);
3851 break;
3852
3853 case OPID_TILE_VISION:
3854 gtk_list_store_clear(store);
3855 player_slots_iterate(pslot) {
3856 id = player_slot_index(pslot);
3857 if (player_slot_is_used(pslot)) {
3858 struct player *pplayer = player_slot_get_player(pslot);
3859 name = player_name(pplayer);
3860 pixbuf = get_flag(pplayer->nation);
3861 } else {
3862 name = "";
3863 pixbuf = NULL;
3864 }
3865 gtk_list_store_append(store, &iter);
3866 gtk_list_store_set(store, &iter, 0, id, 2, name, -1);
3867 if (pixbuf) {
3868 gtk_list_store_set(store, &iter, 1, pixbuf, -1);
3869 g_object_unref(pixbuf);
3870 pixbuf = NULL;
3871 }
3872 present = BV_ISSET(pv->data.v_tile_vision->tile_known, id);
3873 gtk_list_store_set(store, &iter, 3, present, -1);
3874 vision_layer_iterate(v) {
3875 present = BV_ISSET(pv->data.v_tile_vision->tile_seen[v], id);
3876 gtk_list_store_set(store, &iter, 4 + v, present, -1);
3877 } vision_layer_iterate_end;
3878 } player_slots_iterate_end;
3879 break;
3880
3881 case OPID_STARTPOS_NATIONS:
3882 gtk_list_store_clear(store);
3883 gtk_list_store_append(store, &iter);
3884 all = (0 == nation_hash_size(pv->data.v_nation_hash));
3885 gtk_list_store_set(store, &iter, 0, all, 1, -1, 3,
3886 _("All nations"), -1);
3887 nations_iterate(pnation) {
3888 if (client_nation_is_in_current_set(pnation)
3889 && is_nation_playable(pnation)) {
3890 present = (!all && nation_hash_lookup(pv->data.v_nation_hash,
3891 pnation, NULL));
3892 id = nation_number(pnation);
3893 pixbuf = get_flag(pnation);
3894 name = nation_adjective_translation(pnation);
3895 gtk_list_store_append(store, &iter);
3896 gtk_list_store_set(store, &iter, 0, present, 1, id,
3897 2, pixbuf, 3, name, -1);
3898 if (pixbuf) {
3899 g_object_unref(pixbuf);
3900 }
3901 }
3902 } nations_iterate_end;
3903 buf = propval_as_string(pv);
3904 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3905 g_free(buf);
3906 break;
3907
3908 case OPID_CITY_BUILDINGS:
3909 gtk_list_store_clear(store);
3910 improvement_iterate(pimprove) {
3911 if (is_special_improvement(pimprove)) {
3912 continue;
3913 }
3914 id = improvement_index(pimprove);
3915 name = improvement_name_translation(pimprove);
3916 turn_built = pv->data.v_built[id].turn;
3917 present = turn_built >= 0;
3918 buf = built_status_to_string(&pv->data.v_built[id]);
3919 gtk_list_store_append(store, &iter);
3920 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name,
3921 3, buf, -1);
3922 g_free(buf);
3923 } improvement_iterate_end;
3924 buf = propval_as_string(pv);
3925 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3926 g_free(buf);
3927 break;
3928
3929 case OPID_PLAYER_NATION:
3930 {
3931 enum barbarian_type barbarian_type =
3932 nation_barbarian_type(pv->data.v_nation);
3933
3934 gtk_list_store_clear(store);
3935 nations_iterate(pnation) {
3936 if (client_nation_is_in_current_set(pnation)
3937 && nation_barbarian_type(pnation) == barbarian_type
3938 && (barbarian_type != NOT_A_BARBARIAN
3939 || is_nation_playable(pnation))) {
3940 present = (pnation == pv->data.v_nation);
3941 id = nation_index(pnation);
3942 pixbuf = get_flag(pnation);
3943 name = nation_adjective_translation(pnation);
3944 gtk_list_store_append(store, &iter);
3945 gtk_list_store_set(store, &iter, 0, present, 1, id,
3946 2, pixbuf, 3, name, -1);
3947 if (pixbuf) {
3948 g_object_unref(pixbuf);
3949 }
3950 }
3951 } nations_iterate_end;
3952 gtk_label_set_text(GTK_LABEL(ev->panel_label),
3953 nation_adjective_translation(pv->data.v_nation));
3954 pixbuf = get_flag(pv->data.v_nation);
3955 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
3956 if (pixbuf) {
3957 g_object_unref(pixbuf);
3958 }
3959 }
3960 break;
3961
3962 case OPID_PLAYER_GOV:
3963 {
3964 gtk_list_store_clear(store);
3965 governments_iterate(pgov) {
3966 present = (pgov == pv->data.v_gov);
3967 id = government_index(pgov);
3968 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pgov));
3969 name = government_name_translation(pgov);
3970 gtk_list_store_append(store, &iter);
3971 gtk_list_store_set(store, &iter, 0, present, 1, id,
3972 2, pixbuf, 3, name, -1);
3973 if (pixbuf) {
3974 g_object_unref(pixbuf);
3975 }
3976 } governments_iterate_end;
3977 gtk_label_set_text(GTK_LABEL(ev->panel_label),
3978 government_name_translation(pv->data.v_gov));
3979 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
3980 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
3981 if (pixbuf) {
3982 g_object_unref(pixbuf);
3983 }
3984 }
3985 break;
3986
3987 case OPID_PLAYER_INVENTIONS:
3988 gtk_list_store_clear(store);
3989 advance_iterate(A_FIRST, padvance) {
3990 id = advance_index(padvance);
3991 present = BV_ISSET(pv->data.v_bv_inventions, id);
3992 name = advance_name_translation(padvance);
3993 gtk_list_store_append(store, &iter);
3994 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3995 } advance_iterate_end;
3996 buf = propval_as_string(pv);
3997 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3998 g_free(buf);
3999 break;
4000
4001 case OPID_GAME_SCENARIO_AUTHORS:
4002 case OPID_GAME_SCENARIO_DESC:
4003 disable_gobject_callback(G_OBJECT(ev->textbuf),
4004 G_CALLBACK(extviewer_textbuf_changed));
4005 {
4006 GtkTextIter start, end;
4007 char *oldtext;
4008
4009 /* Don't re-set content if unchanged, to avoid moving cursor */
4010 gtk_text_buffer_get_bounds(textbuf, &start, &end);
4011 oldtext = gtk_text_buffer_get_text(textbuf, &start, &end, TRUE);
4012 if (strcmp(oldtext, pv->data.v_const_string) != 0) {
4013 gtk_text_buffer_set_text(textbuf, pv->data.v_const_string, -1);
4014 }
4015 }
4016 enable_gobject_callback(G_OBJECT(ev->textbuf),
4017 G_CALLBACK(extviewer_textbuf_changed));
4018 gtk_widget_set_sensitive(ev->view_widget, TRUE);
4019 buf = propval_as_string(pv);
4020 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4021 g_free(buf);
4022 break;
4023
4024 default:
4025 log_error("Unhandled request to refresh widgets "
4026 "extviewer_refresh_widgets() for objprop id=%d "
4027 "name \"%s\".", propid, objprop_get_name(op));
4028 break;
4029 }
4030 }
4031
4032 /****************************************************************************
4033 Clear the display widgets.
4034 ****************************************************************************/
extviewer_clear_widgets(struct extviewer * ev)4035 static void extviewer_clear_widgets(struct extviewer *ev)
4036 {
4037 struct objprop *op;
4038 enum object_property_ids propid;
4039
4040 if (!ev) {
4041 return;
4042 }
4043
4044 op = extviewer_get_objprop(ev);
4045 propid = objprop_get_id(op);
4046
4047 propval_free(ev->pv_cached);
4048 ev->pv_cached = NULL;
4049
4050 if (ev->panel_label != NULL) {
4051 gtk_label_set_text(GTK_LABEL(ev->panel_label), NULL);
4052 }
4053
4054 switch (propid) {
4055 case OPID_TILE_SPECIALS:
4056 case OPID_TILE_ROADS:
4057 case OPID_TILE_BASES:
4058 case OPID_TILE_VISION:
4059 case OPID_STARTPOS_NATIONS:
4060 case OPID_CITY_BUILDINGS:
4061 case OPID_PLAYER_INVENTIONS:
4062 gtk_list_store_clear(ev->store);
4063 break;
4064 case OPID_PLAYER_NATION:
4065 case OPID_PLAYER_GOV:
4066 gtk_list_store_clear(ev->store);
4067 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), NULL);
4068 break;
4069 case OPID_GAME_SCENARIO_AUTHORS:
4070 case OPID_GAME_SCENARIO_DESC:
4071 disable_gobject_callback(G_OBJECT(ev->textbuf),
4072 G_CALLBACK(extviewer_textbuf_changed));
4073 gtk_text_buffer_set_text(ev->textbuf, "", -1);
4074 enable_gobject_callback(G_OBJECT(ev->textbuf),
4075 G_CALLBACK(extviewer_textbuf_changed));
4076 gtk_widget_set_sensitive(ev->view_widget, FALSE);
4077 break;
4078 default:
4079 log_error("Unhandled request to clear widgets "
4080 "in extviewer_clear_widgets() for objprop id=%d "
4081 "name \"%s\".", propid, objprop_get_name(op));
4082 break;
4083 }
4084 }
4085
4086 /****************************************************************************
4087 Handle the extviewer's panel button click. This should set the property
4088 page to display the view widget for this complex property.
4089 ****************************************************************************/
extviewer_panel_button_clicked(GtkButton * button,gpointer userdata)4090 static void extviewer_panel_button_clicked(GtkButton *button,
4091 gpointer userdata)
4092 {
4093 struct extviewer *ev;
4094 struct property_page *pp;
4095 struct objprop *op;
4096
4097 ev = userdata;
4098 if (!ev) {
4099 return;
4100 }
4101
4102 op = extviewer_get_objprop(ev);
4103 pp = objprop_get_property_page(op);
4104 property_page_show_extviewer(pp, ev);
4105 }
4106
4107 /****************************************************************************
4108 Handle toggling of a boolean cell value in the extviewer's tree view.
4109 ****************************************************************************/
extviewer_view_cell_toggled(GtkCellRendererToggle * cell,gchar * path,gpointer userdata)4110 static void extviewer_view_cell_toggled(GtkCellRendererToggle *cell,
4111 gchar *path,
4112 gpointer userdata)
4113 {
4114 struct extviewer *ev;
4115 struct objprop *op;
4116 struct property_page *pp;
4117 enum object_property_ids propid;
4118 GtkTreeModel *model;
4119 GtkTreeIter iter;
4120 int id, old_id, turn_built;
4121 struct propval *pv;
4122 bool active, present;
4123 gchar *buf;
4124 GdkPixbuf *pixbuf = NULL;
4125
4126 ev = userdata;
4127 if (!ev) {
4128 return;
4129 }
4130
4131 pv = ev->pv_cached;
4132 if (!pv) {
4133 return;
4134 }
4135
4136 op = extviewer_get_objprop(ev);
4137 propid = objprop_get_id(op);
4138 active = gtk_cell_renderer_toggle_get_active(cell);
4139 pp = objprop_get_property_page(op);
4140
4141 model = GTK_TREE_MODEL(ev->store);
4142 if (!gtk_tree_model_get_iter_from_string(model, &iter, path)) {
4143 return;
4144 }
4145 present = !active;
4146
4147
4148 switch (propid) {
4149
4150 case OPID_TILE_SPECIALS:
4151 gtk_tree_model_get(model, &iter, 1, &id, -1);
4152 if (id < 0 || id >= extra_type_list_size(extra_type_list_by_cause(EC_SPECIAL))) {
4153 return;
4154 }
4155 if (present) {
4156 BV_SET(pv->data.v_bv_special, id);
4157 } else {
4158 BV_CLR(pv->data.v_bv_special, id);
4159 }
4160 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4161 buf = propval_as_string(pv);
4162 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4163 g_free(buf);
4164 break;
4165
4166 case OPID_TILE_ROADS:
4167 gtk_tree_model_get(model, &iter, 1, &id, -1);
4168 if (!(0 <= id && id < road_count())) {
4169 return;
4170 }
4171 if (present) {
4172 BV_SET(pv->data.v_bv_roads, id);
4173 } else {
4174 BV_CLR(pv->data.v_bv_roads, id);
4175 }
4176 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4177 buf = propval_as_string(pv);
4178 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4179 g_free(buf);
4180 break;
4181
4182 case OPID_TILE_BASES:
4183 gtk_tree_model_get(model, &iter, 1, &id, -1);
4184 if (!(0 <= id && id < base_count())) {
4185 return;
4186 }
4187 if (present) {
4188 BV_SET(pv->data.v_bv_bases, id);
4189 } else {
4190 BV_CLR(pv->data.v_bv_bases, id);
4191 }
4192 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4193 buf = propval_as_string(pv);
4194 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4195 g_free(buf);
4196 break;
4197
4198 case OPID_STARTPOS_NATIONS:
4199 gtk_tree_model_get(model, &iter, 1, &id, -1);
4200 if (-1 > id && id >= nation_count()) {
4201 return;
4202 }
4203
4204 if (-1 == id) {
4205 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4206 gtk_tree_model_get_iter_first(model, &iter);
4207 if (present) {
4208 while (gtk_tree_model_iter_next(model, &iter)) {
4209 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4210 }
4211 nation_hash_clear(pv->data.v_nation_hash);
4212 } else {
4213 const struct nation_type *pnation;
4214 int id2;
4215
4216 gtk_tree_model_iter_next(model, &iter);
4217 gtk_tree_model_get(model, &iter, 0, &id2, -1);
4218 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4219 pnation = nation_by_number(id2);
4220 nation_hash_insert(pv->data.v_nation_hash, pnation, NULL);
4221 }
4222 } else {
4223 const struct nation_type *pnation;
4224 bool all;
4225
4226 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4227 pnation = nation_by_number(id);
4228 if (present) {
4229 nation_hash_insert(pv->data.v_nation_hash, pnation, NULL);
4230 } else {
4231 nation_hash_remove(pv->data.v_nation_hash, pnation);
4232 }
4233 gtk_tree_model_get_iter_first(model, &iter);
4234 all = (0 == nation_hash_size(pv->data.v_nation_hash));
4235 gtk_list_store_set(ev->store, &iter, 0, all, -1);
4236 }
4237 buf = propval_as_string(pv);
4238 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4239 g_free(buf);
4240 break;
4241
4242 case OPID_CITY_BUILDINGS:
4243 gtk_tree_model_get(model, &iter, 1, &id, -1);
4244 if (!(0 <= id && id < B_LAST)) {
4245 return;
4246 }
4247 turn_built = present ? game.info.turn : I_NEVER;
4248 pv->data.v_built[id].turn = turn_built;
4249 buf = built_status_to_string(&pv->data.v_built[id]);
4250 gtk_list_store_set(ev->store, &iter, 0, present, 3, buf, -1);
4251 g_free(buf);
4252 buf = propval_as_string(pv);
4253 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4254 g_free(buf);
4255 break;
4256
4257 case OPID_PLAYER_NATION:
4258 gtk_tree_model_get(model, &iter, 1, &id, -1);
4259 if (!(0 <= id && id < nation_count()) || !present) {
4260 return;
4261 }
4262 old_id = nation_index(pv->data.v_nation);
4263 pv->data.v_nation = nation_by_number(id);
4264 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4265 gtk_tree_model_iter_nth_child(model, &iter, NULL, old_id);
4266 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4267 gtk_label_set_text(GTK_LABEL(ev->panel_label),
4268 nation_adjective_translation(pv->data.v_nation));
4269 pixbuf = get_flag(pv->data.v_nation);
4270 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
4271 if (pixbuf) {
4272 g_object_unref(pixbuf);
4273 }
4274 break;
4275
4276 case OPID_PLAYER_GOV:
4277 gtk_tree_model_get(model, &iter, 1, &id, -1);
4278 if (!(0 <= id && id < government_count()) || !present) {
4279 return;
4280 }
4281 old_id = government_index(pv->data.v_gov);
4282 pv->data.v_gov = government_by_number(id);
4283 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4284 gtk_tree_model_iter_nth_child(model, &iter, NULL, old_id);
4285 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4286 gtk_label_set_text(GTK_LABEL(ev->panel_label),
4287 government_name_translation(pv->data.v_gov));
4288 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
4289 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
4290 if (pixbuf) {
4291 g_object_unref(pixbuf);
4292 }
4293 break;
4294
4295 case OPID_PLAYER_INVENTIONS:
4296 gtk_tree_model_get(model, &iter, 1, &id, -1);
4297 if (!(A_FIRST <= id && id < advance_count())) {
4298 return;
4299 }
4300 if (present) {
4301 BV_SET(pv->data.v_bv_inventions, id);
4302 } else {
4303 BV_CLR(pv->data.v_bv_inventions, id);
4304 }
4305 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4306 buf = propval_as_string(pv);
4307 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4308 g_free(buf);
4309 break;
4310
4311 default:
4312 log_error("Unhandled widget toggled signal in "
4313 "extviewer_view_cell_toggled() for objprop id=%d "
4314 "name \"%s\".", propid, objprop_get_name(op));
4315 return;
4316 break;
4317 }
4318
4319 property_page_change_value(pp, op, pv);
4320 }
4321
4322 /****************************************************************************
4323 Handle a change in the extviewer's text buffer.
4324 ****************************************************************************/
extviewer_textbuf_changed(GtkTextBuffer * textbuf,gpointer userdata)4325 static void extviewer_textbuf_changed(GtkTextBuffer *textbuf,
4326 gpointer userdata)
4327 {
4328 struct extviewer *ev;
4329 struct objprop *op;
4330 struct property_page *pp;
4331 enum object_property_ids propid;
4332 struct propval value = {{0,}, VALTYPE_STRING, FALSE}, *pv;
4333 GtkTextIter start, end;
4334 char *text;
4335 gchar *buf;
4336
4337 ev = userdata;
4338 if (!ev) {
4339 return;
4340 }
4341
4342 op = extviewer_get_objprop(ev);
4343 propid = objprop_get_id(op);
4344 pp = objprop_get_property_page(op);
4345
4346 gtk_text_buffer_get_start_iter(textbuf, &start);
4347 gtk_text_buffer_get_end_iter(textbuf, &end);
4348 text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
4349 value.data.v_const_string = text;
4350 pv = &value;
4351
4352 switch (propid) {
4353 case OPID_GAME_SCENARIO_AUTHORS:
4354 case OPID_GAME_SCENARIO_DESC:
4355 buf = propval_as_string(pv);
4356 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4357 g_free(buf);
4358 break;
4359 default:
4360 log_error("Unhandled widget modified signal in "
4361 "extviewer_textbuf_changed() for objprop id=%d "
4362 "name \"%s\".", propid, objprop_get_name(op));
4363 return;
4364 break;
4365 }
4366
4367 property_page_change_value(pp, op, pv);
4368 g_free(text);
4369 }
4370
4371 /****************************************************************************
4372 Install all object properties that this page type can view/edit.
4373 ****************************************************************************/
property_page_setup_objprops(struct property_page * pp)4374 static void property_page_setup_objprops(struct property_page *pp)
4375 {
4376 #define ADDPROP(ARG_id, ARG_name, ARG_tooltip, ARG_flags, ARG_valtype) do { \
4377 struct objprop *MY_op = objprop_new(ARG_id, ARG_name, ARG_tooltip, \
4378 ARG_flags, ARG_valtype, pp); \
4379 objprop_hash_insert(pp->objprop_table, MY_op->id, MY_op); \
4380 } while (FALSE)
4381
4382 switch (property_page_get_objtype(pp)) {
4383 case OBJTYPE_TILE:
4384 ADDPROP(OPID_TILE_IMAGE, _("Image"), NULL,
4385 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4386 ADDPROP(OPID_TILE_TERRAIN, _("Terrain"), NULL,
4387 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4388 ADDPROP(OPID_TILE_RESOURCE, _("Resource"), NULL,
4389 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4390 ADDPROP(OPID_TILE_INDEX, _("Index"), NULL,
4391 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4392 ADDPROP(OPID_TILE_X, Q_("?coordinate:X"), NULL,
4393 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4394 ADDPROP(OPID_TILE_Y, Q_("?coordinate:Y"), NULL,
4395 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4396 /* TRANS: The coordinate X in native coordinates.
4397 * The freeciv coordinate system is described in doc/HACKING. */
4398 ADDPROP(OPID_TILE_NAT_X, _("NAT X"), NULL,
4399 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4400 /* TRANS: The coordinate Y in native coordinates.
4401 * The freeciv coordinate system is described in doc/HACKING. */
4402 ADDPROP(OPID_TILE_NAT_Y, _("NAT Y"), NULL,
4403 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4404 ADDPROP(OPID_TILE_CONTINENT, _("Continent"), NULL,
4405 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4406 ADDPROP(OPID_TILE_XY, Q_("?coordinates:X,Y"), NULL,
4407 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4408 ADDPROP(OPID_TILE_SPECIALS, _("Specials"), NULL,
4409 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4410 VALTYPE_BV_SPECIAL);
4411 ADDPROP(OPID_TILE_ROADS, _("Roads"), NULL,
4412 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4413 VALTYPE_BV_ROADS);
4414 ADDPROP(OPID_TILE_BASES, _("Bases"), NULL,
4415 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4416 VALTYPE_BV_BASES);
4417 #ifdef FREECIV_DEBUG
4418 ADDPROP(OPID_TILE_ADDRESS, _("Address"), NULL,
4419 OPF_HAS_WIDGET, VALTYPE_STRING);
4420 #endif /* FREECIV_DEBUG */
4421 #if 0
4422 /* Disabled entirely for now as server is not sending other
4423 * players' vision information anyway. */
4424 ADDPROP(OPID_TILE_VISION, _("Vision"), NULL,
4425 OPF_HAS_WIDGET, VALTYPE_TILE_VISION_DATA);
4426 #endif
4427 /* TRANS: Tile property "Label" label in editor */
4428 ADDPROP(OPID_TILE_LABEL, Q_("?property:Label"), NULL,
4429 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_STRING);
4430 return;
4431
4432 case OBJTYPE_STARTPOS:
4433 ADDPROP(OPID_STARTPOS_IMAGE, _("Image"), NULL,
4434 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4435 ADDPROP(OPID_STARTPOS_XY, Q_("?coordinates:X,Y"), NULL,
4436 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4437 ADDPROP(OPID_STARTPOS_EXCLUDE, _("Exclude Nations"), NULL,
4438 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4439 ADDPROP(OPID_STARTPOS_NATIONS, _("Nations"), NULL,
4440 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4441 VALTYPE_NATION_HASH);
4442 return;
4443
4444 case OBJTYPE_UNIT:
4445 ADDPROP(OPID_UNIT_IMAGE, _("Image"), NULL,
4446 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4447 #ifdef FREECIV_DEBUG
4448 ADDPROP(OPID_UNIT_ADDRESS, _("Address"), NULL,
4449 OPF_HAS_WIDGET, VALTYPE_STRING);
4450 #endif /* FREECIV_DEBUG */
4451 ADDPROP(OPID_UNIT_TYPE, _("Type"), NULL,
4452 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4453 ADDPROP(OPID_UNIT_ID, _("ID"), NULL,
4454 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4455 ADDPROP(OPID_UNIT_XY, Q_("?coordinates:X,Y"), NULL,
4456 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4457 ADDPROP(OPID_UNIT_MOVES_LEFT, _("Moves Left"), NULL,
4458 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4459 ADDPROP(OPID_UNIT_FUEL, _("Fuel"), NULL,
4460 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4461 ADDPROP(OPID_UNIT_MOVED, _("Moved"), NULL,
4462 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4463 ADDPROP(OPID_UNIT_DONE_MOVING, _("Done Moving"), NULL,
4464 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4465 /* TRANS: HP = Hit Points of a unit. */
4466 ADDPROP(OPID_UNIT_HP, _("HP"), NULL,
4467 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4468 ADDPROP(OPID_UNIT_VETERAN, _("Veteran"), NULL,
4469 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4470 return;
4471
4472 case OBJTYPE_CITY:
4473 ADDPROP(OPID_CITY_IMAGE, _("Image"), NULL,
4474 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4475 ADDPROP(OPID_CITY_NAME, _("Name"), NULL,
4476 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_STRING);
4477 #ifdef FREECIV_DEBUG
4478 ADDPROP(OPID_CITY_ADDRESS, _("Address"), NULL,
4479 OPF_HAS_WIDGET, VALTYPE_STRING);
4480 #endif /* FREECIV_DEBUG */
4481 ADDPROP(OPID_CITY_ID, _("ID"), NULL,
4482 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4483 ADDPROP(OPID_CITY_XY, Q_("?coordinates:X,Y"), NULL,
4484 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4485 ADDPROP(OPID_CITY_SIZE, _("Size"), NULL,
4486 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4487 ADDPROP(OPID_CITY_HISTORY, _("History"), NULL,
4488 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4489 ADDPROP(OPID_CITY_BUILDINGS, _("Buildings"), NULL,
4490 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4491 VALTYPE_BUILT_ARRAY);
4492 ADDPROP(OPID_CITY_FOOD_STOCK, _("Food Stock"), NULL,
4493 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4494 ADDPROP(OPID_CITY_SHIELD_STOCK, _("Shield Stock"), NULL,
4495 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4496 return;
4497
4498 case OBJTYPE_PLAYER:
4499 ADDPROP(OPID_PLAYER_NAME, _("Name"), NULL,
4500 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4501 VALTYPE_STRING);
4502 #ifdef FREECIV_DEBUG
4503 ADDPROP(OPID_PLAYER_ADDRESS, _("Address"), NULL,
4504 OPF_HAS_WIDGET, VALTYPE_STRING);
4505 #endif /* FREECIV_DEBUG */
4506 ADDPROP(OPID_PLAYER_NATION, _("Nation"), NULL,
4507 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4508 VALTYPE_NATION);
4509 ADDPROP(OPID_PLAYER_GOV, _("Government"), NULL,
4510 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4511 VALTYPE_GOV);
4512 ADDPROP(OPID_PLAYER_AGE, _("Age"), NULL,
4513 OPF_HAS_WIDGET, VALTYPE_INT);
4514 ADDPROP(OPID_PLAYER_INVENTIONS, _("Inventions"), NULL,
4515 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4516 VALTYPE_INVENTIONS_ARRAY);
4517 ADDPROP(OPID_PLAYER_SCIENCE, _("Science"), NULL,
4518 OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4519 ADDPROP(OPID_PLAYER_GOLD, _("Gold"), NULL,
4520 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4521 VALTYPE_INT);
4522 return;
4523
4524 case OBJTYPE_GAME:
4525 ADDPROP(OPID_GAME_YEAR, _("Year"), NULL,
4526 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4527 VALTYPE_INT);
4528 ADDPROP(OPID_GAME_SCENARIO, _("Scenario"), NULL,
4529 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4530 VALTYPE_BOOL);
4531 ADDPROP(OPID_GAME_SCENARIO_NAME, _("Scenario Name"), NULL,
4532 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4533 VALTYPE_STRING);
4534 ADDPROP(OPID_GAME_SCENARIO_AUTHORS, _("Scenario Authors"), NULL,
4535 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4536 VALTYPE_STRING);
4537 ADDPROP(OPID_GAME_SCENARIO_DESC, _("Scenario Description"), NULL,
4538 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4539 VALTYPE_STRING);
4540 ADDPROP(OPID_GAME_SCENARIO_RANDSTATE,
4541 _("Save Random Number State"), NULL,
4542 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4543 ADDPROP(OPID_GAME_SCENARIO_PLAYERS, _("Save Players"), NULL,
4544 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4545 ADDPROP(OPID_GAME_STARTPOS_NATIONS,
4546 _("Nation Start Positions"), NULL,
4547 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4548 ADDPROP(OPID_GAME_PREVENT_CITIES,
4549 _("Prevent New Cities"), NULL,
4550 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4551 ADDPROP(OPID_GAME_LAKE_FLOODING,
4552 _("Saltwater Flooding Lakes"), NULL,
4553 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4554 return;
4555
4556 case NUM_OBJTYPES:
4557 break;
4558 }
4559
4560 log_error("%s(): Unhandled page object type %s (nb %d).", __FUNCTION__,
4561 objtype_get_name(property_page_get_objtype(pp)),
4562 property_page_get_objtype(pp));
4563 #undef ADDPROP
4564 }
4565
4566 /****************************************************************************
4567 Callback for when a property page's listview's selection changes.
4568 ****************************************************************************/
property_page_selection_changed(GtkTreeSelection * sel,gpointer userdata)4569 static void property_page_selection_changed(GtkTreeSelection *sel,
4570 gpointer userdata)
4571 {
4572 struct property_page *pp;
4573 struct objbind *ob = NULL;
4574
4575 pp = userdata;
4576 if (!pp) {
4577 return;
4578 }
4579
4580 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
4581 property_page_set_focused_objbind(pp, NULL);
4582 }
4583
4584 ob = property_page_get_focused_objbind(pp);
4585 property_page_objprop_iterate(pp, op) {
4586 objprop_refresh_widget(op, ob);
4587 } property_page_objprop_iterate_end;
4588 }
4589
4590 /****************************************************************************
4591 Monitor which rows are to be selected, so we know which objbind to display
4592 in the properties panel.
4593 ****************************************************************************/
property_page_selection_func(GtkTreeSelection * sel,GtkTreeModel * model,GtkTreePath * sel_path,gboolean currently_selected,gpointer data)4594 static gboolean property_page_selection_func(GtkTreeSelection *sel,
4595 GtkTreeModel *model,
4596 GtkTreePath *sel_path,
4597 gboolean currently_selected,
4598 gpointer data)
4599 {
4600 struct property_page *pp;
4601 struct objbind *ob = NULL, *old_ob;
4602 GtkTreeIter iter;
4603
4604 pp = data;
4605 if (!pp || !sel_path) {
4606 return TRUE;
4607 }
4608
4609 if (!gtk_tree_model_get_iter(model, &iter, sel_path)) {
4610 return TRUE;
4611 }
4612
4613 old_ob = property_page_get_focused_objbind(pp);
4614 gtk_tree_model_get(model, &iter, 0, &ob, -1);
4615 if (currently_selected) {
4616 if (ob == old_ob) {
4617 GList *rows, *p;
4618 GtkTreePath *path;
4619 struct objbind *new_ob = NULL;
4620
4621 rows = gtk_tree_selection_get_selected_rows(sel, NULL);
4622 for (p = rows; p != NULL; p = p->next) {
4623 path = p->data;
4624 if (gtk_tree_model_get_iter(model, &iter, path)) {
4625 struct objbind *test_ob = NULL;
4626 gtk_tree_model_get(model, &iter, 0, &test_ob, -1);
4627 if (test_ob == ob) {
4628 continue;
4629 }
4630 new_ob = test_ob;
4631 break;
4632 }
4633 }
4634 g_list_foreach(rows, (GFunc) gtk_tree_path_free, NULL);
4635 g_list_free(rows);
4636
4637 property_page_set_focused_objbind(pp, new_ob);
4638 }
4639 } else {
4640 property_page_set_focused_objbind(pp, ob);
4641 }
4642
4643 return TRUE;
4644 }
4645
4646 /****************************************************************************
4647 Callback to handle text changing in the quick find entry widget.
4648 ****************************************************************************/
property_page_quick_find_entry_changed(GtkWidget * entry,gpointer userdata)4649 static void property_page_quick_find_entry_changed(GtkWidget *entry,
4650 gpointer userdata)
4651 {
4652 struct property_page *pp;
4653 const gchar *text;
4654 GtkWidget *w;
4655 GtkTreeViewColumn *col;
4656 struct property_filter *pf;
4657 bool matched;
4658
4659 pp = userdata;
4660 text = gtk_entry_get_text(GTK_ENTRY(entry));
4661 pf = property_filter_new(text);
4662
4663 property_page_objprop_iterate(pp, op) {
4664 if (!objprop_has_widget(op)
4665 && !objprop_show_in_listview(op)) {
4666 continue;
4667 }
4668 matched = property_filter_match(pf, op);
4669 w = objprop_get_widget(op);
4670 if (objprop_has_widget(op) && w != NULL) {
4671 if (matched) {
4672 gtk_widget_show(w);
4673 } else {
4674 gtk_widget_hide(w);
4675 }
4676 }
4677 col = objprop_get_treeview_column(op);
4678 if (objprop_show_in_listview(op) && col != NULL) {
4679 gtk_tree_view_column_set_visible(col, matched);
4680 }
4681 } property_page_objprop_iterate_end;
4682
4683 property_filter_free(pf);
4684 }
4685
4686 /****************************************************************************
4687 Create and return a property page of the given object type.
4688 Returns NULL if the page could not be created.
4689 ****************************************************************************/
4690 static struct property_page *
property_page_new(enum editor_object_type objtype,struct property_editor * pe)4691 property_page_new(enum editor_object_type objtype,
4692 struct property_editor *pe)
4693 {
4694 struct property_page *pp;
4695 GtkWidget *vbox, *vbox2, *hbox, *hbox2, *paned, *frame, *w;
4696 GtkWidget *scrollwin, *view, *label, *entry, *notebook;
4697 GtkWidget *button, *hsep, *image;
4698 GtkTreeSelection *sel;
4699 GtkCellRenderer *cell;
4700 GtkTreeViewColumn *col;
4701 GtkSizeGroup *sizegroup;
4702 int num_columns = 0;
4703 GType *gtype_array;
4704 int col_id = 1;
4705 const char *attr_type_str, *name, *tooltip;
4706 gchar *title;
4707
4708 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
4709 return NULL;
4710 }
4711
4712 pp = fc_calloc(1, sizeof(struct property_page));
4713 pp->objtype = objtype;
4714 pp->pe_parent = pe;
4715
4716 sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
4717
4718 pp->objprop_table = objprop_hash_new();
4719 property_page_setup_objprops(pp);
4720
4721 pp->objbind_table = objbind_hash_new();
4722
4723 pp->tag_table = stored_tag_hash_new();
4724
4725 property_page_objprop_iterate(pp, op) {
4726 if (objprop_show_in_listview(op)) {
4727 num_columns++;
4728 }
4729 } property_page_objprop_iterate_end;
4730
4731 /* Column zero in the store holds an objbind
4732 * pointer and is never displayed. */
4733 num_columns++;
4734 gtype_array = fc_malloc(num_columns * sizeof(GType));
4735 gtype_array[0] = G_TYPE_POINTER;
4736
4737 property_page_objprop_iterate(pp, op) {
4738 if (objprop_show_in_listview(op)) {
4739 gtype_array[col_id] = objprop_get_gtype(op);
4740 objprop_set_column_id(op, col_id);
4741 col_id++;
4742 }
4743 } property_page_objprop_iterate_end;
4744
4745 pp->object_store = gtk_list_store_newv(num_columns, gtype_array);
4746 free(gtype_array);
4747
4748 paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
4749 gtk_paned_set_position(GTK_PANED(paned), 256);
4750 pp->widget = paned;
4751
4752 /* Left side object list view. */
4753
4754 vbox = gtk_grid_new();
4755 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
4756 GTK_ORIENTATION_VERTICAL);
4757 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
4758 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
4759 gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
4760
4761 scrollwin = gtk_scrolled_window_new(NULL, NULL);
4762 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
4763 GTK_SHADOW_ETCHED_IN);
4764 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
4765 GTK_POLICY_AUTOMATIC,
4766 GTK_POLICY_AUTOMATIC);
4767 gtk_container_add(GTK_CONTAINER(vbox), scrollwin);
4768
4769 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pp->object_store));
4770 gtk_widget_set_hexpand(view, TRUE);
4771 gtk_widget_set_vexpand(view, TRUE);
4772 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
4773
4774 property_page_objprop_iterate(pp, op) {
4775 if (!objprop_show_in_listview(op)) {
4776 continue;
4777 }
4778
4779 attr_type_str = objprop_get_attribute_type_string(op);
4780 if (!attr_type_str) {
4781 continue;
4782 }
4783 col_id = objprop_get_column_id(op);
4784 if (col_id < 0) {
4785 continue;
4786 }
4787 name = objprop_get_name(op);
4788 if (!name) {
4789 continue;
4790 }
4791 cell = objprop_create_cell_renderer(op);
4792 if (!cell) {
4793 continue;
4794 }
4795
4796 col = gtk_tree_view_column_new_with_attributes(name, cell,
4797 attr_type_str, col_id,
4798 NULL);
4799
4800 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
4801 gtk_tree_view_column_set_resizable(col, TRUE);
4802 gtk_tree_view_column_set_reorderable(col, TRUE);
4803 if (objprop_is_sortable(op)) {
4804 gtk_tree_view_column_set_clickable(col, TRUE);
4805 gtk_tree_view_column_set_sort_column_id(col, col_id);
4806 } else {
4807 gtk_tree_view_column_set_clickable(col, FALSE);
4808 }
4809 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
4810 objprop_set_treeview_column(op, col);
4811
4812 } property_page_objprop_iterate_end;
4813
4814 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
4815 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
4816 g_signal_connect(sel, "changed",
4817 G_CALLBACK(property_page_selection_changed), pp);
4818 gtk_tree_selection_set_select_function(sel,
4819 property_page_selection_func, pp, NULL);
4820
4821 gtk_container_add(GTK_CONTAINER(scrollwin), view);
4822 pp->object_view = view;
4823
4824 if (!objtype_is_conserved(objtype)) {
4825 hbox = gtk_grid_new();
4826 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
4827 gtk_container_add(GTK_CONTAINER(vbox), hbox);
4828
4829 button = gtk_button_new();
4830 image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
4831 gtk_button_set_image(GTK_BUTTON(button), image);
4832 gtk_button_set_label(GTK_BUTTON(button), _("Create"));
4833 gtk_size_group_add_widget(sizegroup, button);
4834 gtk_widget_set_tooltip_text(button,
4835 _("Pressing this button will create a new object of the "
4836 "same type as the current property page and add it to "
4837 "the page. The specific type and count of the objects "
4838 "is taken from the editor tool state. So for example, "
4839 "the \"tool value\" of the unit tool and its \"count\" "
4840 "parameter affect unit creation."));
4841 g_signal_connect(button, "clicked",
4842 G_CALLBACK(property_page_create_button_clicked), pp);
4843 gtk_container_add(GTK_CONTAINER(hbox), button);
4844
4845 button = gtk_button_new();
4846 image = gtk_image_new_from_stock(GTK_STOCK_REMOVE,
4847 GTK_ICON_SIZE_BUTTON);
4848 gtk_button_set_image(GTK_BUTTON(button), image);
4849 gtk_button_set_label(GTK_BUTTON(button), _("Destroy"));
4850 gtk_size_group_add_widget(sizegroup, button);
4851 gtk_widget_set_tooltip_text(button,
4852 _("Pressing this button will send a request to the server "
4853 "to destroy (i.e. erase) the objects selected in the object "
4854 "list."));
4855 g_signal_connect(button, "clicked",
4856 G_CALLBACK(property_page_destroy_button_clicked), pp);
4857 gtk_container_add(GTK_CONTAINER(hbox), button);
4858 }
4859
4860 /* Right side properties panel. */
4861
4862 hbox = gtk_grid_new();
4863 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
4864 gtk_paned_pack2(GTK_PANED(paned), hbox, TRUE, TRUE);
4865
4866 vbox = gtk_grid_new();
4867 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
4868 GTK_ORIENTATION_VERTICAL);
4869 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
4870 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
4871 gtk_container_add(GTK_CONTAINER(hbox), vbox);
4872
4873 /* Extended property viewer to the right of the properties panel.
4874 * This needs to be created before property widgets, since some
4875 * might try to append themselves to this notebook. */
4876
4877 vbox2 = gtk_grid_new();
4878 gtk_widget_set_hexpand(vbox2, TRUE);
4879 gtk_widget_set_vexpand(vbox2, TRUE);
4880 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox2),
4881 GTK_ORIENTATION_VERTICAL);
4882 gtk_grid_set_row_spacing(GTK_GRID(vbox2), 4);
4883 gtk_container_add(GTK_CONTAINER(hbox), vbox2);
4884
4885 notebook = gtk_notebook_new();
4886 gtk_widget_set_vexpand(notebook, TRUE);
4887 gtk_widget_set_size_request(notebook, 256, -1);
4888 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
4889 gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
4890 gtk_container_add(GTK_CONTAINER(vbox2), notebook);
4891 pp->extviewer_notebook = notebook;
4892
4893 hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
4894 gtk_container_add(GTK_CONTAINER(vbox2), hsep);
4895
4896 hbox2 = gtk_grid_new();
4897 gtk_container_set_border_width(GTK_CONTAINER(hbox2), 4);
4898 gtk_container_add(GTK_CONTAINER(vbox2), hbox2);
4899
4900 button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
4901 gtk_size_group_add_widget(sizegroup, button);
4902 g_signal_connect_swapped(button, "clicked",
4903 G_CALLBACK(gtk_widget_hide_on_delete), pe->widget);
4904 gtk_container_add(GTK_CONTAINER(hbox2), button);
4905
4906 /* Now create the properties panel. */
4907
4908 /* TRANS: %s is a type of object that can be edited, such as "Tile",
4909 * "Unit", "Start Position", etc. */
4910 title = g_strdup_printf(_("%s Properties"),
4911 objtype_get_name(objtype));
4912 frame = gtk_frame_new(title);
4913 g_free(title);
4914 gtk_widget_set_size_request(frame, 256, -1);
4915 gtk_container_add(GTK_CONTAINER(vbox), frame);
4916
4917 scrollwin = gtk_scrolled_window_new(NULL, NULL);
4918 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
4919 GTK_SHADOW_NONE);
4920 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
4921 GTK_POLICY_AUTOMATIC,
4922 GTK_POLICY_AUTOMATIC);
4923 gtk_container_add(GTK_CONTAINER(frame), scrollwin);
4924
4925 vbox2 = gtk_grid_new();
4926 gtk_widget_set_vexpand(vbox2, TRUE);
4927 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox2),
4928 GTK_ORIENTATION_VERTICAL);
4929 gtk_grid_set_row_spacing(GTK_GRID(vbox2), 4);
4930 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 4);
4931 gtk_container_add(GTK_CONTAINER(scrollwin), vbox2);
4932
4933 property_page_objprop_iterate(pp, op) {
4934 if (!objprop_has_widget(op)) {
4935 continue;
4936 }
4937 w = objprop_get_widget(op);
4938 if (!w) {
4939 continue;
4940 }
4941 gtk_container_add(GTK_CONTAINER(vbox2), w);
4942 tooltip = objprop_get_tooltip(op);
4943 if (NULL != tooltip) {
4944 gtk_widget_set_tooltip_text(w, tooltip);
4945 }
4946 } property_page_objprop_iterate_end;
4947
4948 hbox2 = gtk_grid_new();
4949 gtk_widget_set_margin_top(hbox2, 4);
4950 gtk_widget_set_margin_bottom(hbox2, 4);
4951 gtk_grid_set_column_spacing(GTK_GRID(hbox2), 4);
4952 gtk_container_add(GTK_CONTAINER(vbox), hbox2);
4953
4954 label = gtk_label_new(_("Filter:"));
4955 gtk_container_add(GTK_CONTAINER(hbox2), label);
4956
4957 entry = gtk_entry_new();
4958 gtk_widget_set_tooltip_text(entry,
4959 _("Enter a filter string to limit which properties are shown. "
4960 "The filter is one or more text patterns separated by | "
4961 "(\"or\") or & (\"and\"). The symbol & has higher precedence "
4962 "than |. A pattern may also be negated by prefixing it with !."));
4963 g_signal_connect(entry, "changed",
4964 G_CALLBACK(property_page_quick_find_entry_changed), pp);
4965 gtk_container_add(GTK_CONTAINER(hbox2), entry);
4966
4967 hbox2 = gtk_grid_new();
4968 gtk_grid_set_column_spacing(GTK_GRID(hbox2), 4);
4969 gtk_container_add(GTK_CONTAINER(vbox), hbox2);
4970
4971 button = gtk_button_new_from_stock(GTK_STOCK_REFRESH);
4972 gtk_size_group_add_widget(sizegroup, button);
4973 gtk_widget_set_tooltip_text(button,
4974 _("Pressing this button will reset all modified properties of "
4975 "the selected objects to their current values (the values "
4976 "they have on the server)."));
4977 g_signal_connect(button, "clicked",
4978 G_CALLBACK(property_page_refresh_button_clicked), pp);
4979 gtk_container_add(GTK_CONTAINER(hbox2), button);
4980
4981 button = gtk_button_new_from_stock(GTK_STOCK_APPLY);
4982 gtk_size_group_add_widget(sizegroup, button);
4983 gtk_widget_set_tooltip_text(button,
4984 _("Pressing this button will send all modified properties of "
4985 "the objects selected in the object list to the server. "
4986 "Modified properties' names are shown in red in the properties "
4987 "panel."));
4988 g_signal_connect(button, "clicked",
4989 G_CALLBACK(property_page_apply_button_clicked), pp);
4990 gtk_container_add(GTK_CONTAINER(hbox2), button);
4991
4992 return pp;
4993 }
4994
4995 /****************************************************************************
4996 Returns the translated name of the property page's object type.
4997 ****************************************************************************/
property_page_get_name(const struct property_page * pp)4998 static const char *property_page_get_name(const struct property_page *pp)
4999 {
5000 if (!pp) {
5001 return "";
5002 }
5003 return objtype_get_name(property_page_get_objtype(pp));
5004 }
5005
5006 /****************************************************************************
5007 Returns the object type for this property page, or -1 if none.
5008 ****************************************************************************/
5009 static enum editor_object_type
property_page_get_objtype(const struct property_page * pp)5010 property_page_get_objtype(const struct property_page *pp)
5011 {
5012 if (!pp) {
5013 return -1;
5014 }
5015 return pp->objtype;
5016 }
5017
5018 /****************************************************************************
5019 Create a pixbuf containing an image of the given tile. The image will
5020 only be of the layers containing terrains, resources and specials.
5021
5022 May return NULL on error or bad input.
5023 NB: You must call g_object_unref on the non-NULL return value when you
5024 no longer need it.
5025 ****************************************************************************/
create_tile_pixbuf(const struct tile * ptile)5026 static GdkPixbuf *create_tile_pixbuf(const struct tile *ptile)
5027 {
5028 int layers[] = {
5029 LAYER_BACKGROUND,
5030 LAYER_TERRAIN1,
5031 LAYER_TERRAIN2,
5032 LAYER_TERRAIN3,
5033 LAYER_WATER,
5034 LAYER_ROADS,
5035 LAYER_SPECIAL1,
5036 LAYER_SPECIAL2,
5037 LAYER_SPECIAL3
5038 };
5039 int num_layers = ARRAY_SIZE(layers);
5040
5041 return create_pixbuf_from_layers(ptile, NULL, NULL, layers, num_layers);
5042 }
5043
5044 /****************************************************************************
5045 Create a pixbuf containing an image of the given unit.
5046
5047 May return NULL on error or bad input.
5048 NB: You must call g_object_unref on the non-NULL return value when you
5049 no longer need it.
5050 ****************************************************************************/
create_unit_pixbuf(const struct unit * punit)5051 static GdkPixbuf *create_unit_pixbuf(const struct unit *punit)
5052 {
5053 int layers[] = {
5054 LAYER_UNIT,
5055 LAYER_FOCUS_UNIT,
5056 };
5057 int num_layers = ARRAY_SIZE(layers);
5058
5059 return create_pixbuf_from_layers(NULL, punit, NULL,
5060 layers, num_layers);
5061 }
5062
5063 /****************************************************************************
5064 Create a pixbuf containing an image of the given city.
5065
5066 May return NULL on error or bad input.
5067 NB: You must call g_object_unref on the non-NULL return value when you
5068 no longer need it.
5069 ****************************************************************************/
create_city_pixbuf(const struct city * pcity)5070 static GdkPixbuf *create_city_pixbuf(const struct city *pcity)
5071 {
5072 int layers[] = {
5073 LAYER_BACKGROUND,
5074 LAYER_TERRAIN1,
5075 LAYER_TERRAIN2,
5076 LAYER_TERRAIN3,
5077 LAYER_WATER,
5078 LAYER_ROADS,
5079 LAYER_SPECIAL1,
5080 LAYER_CITY1,
5081 LAYER_SPECIAL2,
5082 LAYER_CITY2,
5083 LAYER_SPECIAL3
5084 };
5085 int num_layers = ARRAY_SIZE(layers);
5086
5087 return create_pixbuf_from_layers(city_tile(pcity), NULL, pcity,
5088 layers, num_layers);
5089 }
5090
5091 /****************************************************************************
5092 Create a pixbuf containing an image of the given tile, unit or city
5093 restricted to the layers listed in 'layers'.
5094
5095 May return NULL on error or bad input.
5096 NB: You must call g_object_unref on the non-NULL return value when you
5097 no longer need it.
5098 ****************************************************************************/
create_pixbuf_from_layers(const struct tile * ptile,const struct unit * punit,const struct city * pcity,int * layers,int num_layers)5099 static GdkPixbuf *create_pixbuf_from_layers(const struct tile *ptile,
5100 const struct unit *punit,
5101 const struct city *pcity,
5102 int *layers,
5103 int num_layers)
5104 {
5105 struct canvas canvas = FC_STATIC_CANVAS_INIT;
5106 int h, i, fh, fw, canvas_x, canvas_y;
5107 GdkPixbuf *pixbuf;
5108 cairo_t *cr;
5109
5110 fw = tileset_full_tile_width(tileset);
5111 fh = tileset_full_tile_height(tileset);
5112 h = tileset_tile_height(tileset);
5113
5114 canvas.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, fw, fh);
5115
5116 cr = cairo_create(canvas.surface);
5117 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
5118 cairo_paint(cr);
5119 cairo_destroy(cr);
5120
5121 canvas_x = 0;
5122 canvas_y = 0;
5123
5124 canvas_y += (fh - h);
5125
5126 for (i = 0; i < num_layers; i++) {
5127 put_one_element(&canvas, 1.0, layers[i],
5128 ptile, NULL, NULL, punit, pcity,
5129 canvas_x, canvas_y, NULL, NULL);
5130 }
5131 pixbuf = surface_get_pixbuf(canvas.surface, fw, fh);
5132 cairo_surface_destroy(canvas.surface);
5133
5134 return pixbuf;
5135 }
5136
5137 /****************************************************************************
5138 Remove all object binds (i.e. objects listed) in the property page.
5139 ****************************************************************************/
property_page_clear_objbinds(struct property_page * pp)5140 static void property_page_clear_objbinds(struct property_page *pp)
5141 {
5142 if (!pp) {
5143 return;
5144 }
5145
5146 gtk_list_store_clear(pp->object_store);
5147 objbind_hash_clear(pp->objbind_table);
5148 property_page_set_focused_objbind(pp, NULL);
5149 }
5150
5151 /****************************************************************************
5152 Create a new object bind to the given object and register it with the
5153 given property page.
5154 ****************************************************************************/
property_page_add_objbind(struct property_page * pp,gpointer object_data)5155 static void property_page_add_objbind(struct property_page *pp,
5156 gpointer object_data)
5157 {
5158 struct objbind *ob;
5159 enum editor_object_type objtype;
5160 int id;
5161
5162 if (!pp) {
5163 return;
5164 }
5165
5166 objtype = property_page_get_objtype(pp);
5167 id = objtype_get_id_from_object(objtype, object_data);
5168 if (id < 0) {
5169 return;
5170 }
5171
5172 if (objbind_hash_lookup(pp->objbind_table, id, NULL)) {
5173 /* Object already exists. */
5174 return;
5175 }
5176
5177 ob = objbind_new(objtype, object_data);
5178 if (!ob) {
5179 return;
5180 }
5181
5182 objbind_bind_properties(ob, pp);
5183
5184 objbind_hash_insert(pp->objbind_table, ob->object_id, ob);
5185 }
5186
5187 /****************************************************************************
5188 Create zero or more object binds from the objects on the given tile to
5189 the properties contained in the given property page.
5190 ****************************************************************************/
property_page_add_objbinds_from_tile(struct property_page * pp,const struct tile * ptile)5191 static void property_page_add_objbinds_from_tile(struct property_page *pp,
5192 const struct tile *ptile)
5193 {
5194
5195 if (!pp || !ptile) {
5196 return;
5197 }
5198
5199 switch (property_page_get_objtype(pp)) {
5200 case OBJTYPE_TILE:
5201 property_page_add_objbind(pp, (gpointer) ptile);
5202 return;
5203
5204 case OBJTYPE_STARTPOS:
5205 {
5206 struct startpos *psp = map_startpos_get(ptile);
5207
5208 if (NULL != psp) {
5209 property_page_add_objbind(pp, map_startpos_get(ptile));
5210 }
5211 }
5212 return;
5213
5214 case OBJTYPE_UNIT:
5215 unit_list_iterate(ptile->units, punit) {
5216 property_page_add_objbind(pp, punit);
5217 } unit_list_iterate_end;
5218 return;
5219
5220 case OBJTYPE_CITY:
5221 if (tile_city(ptile)) {
5222 property_page_add_objbind(pp, tile_city(ptile));
5223 }
5224 return;
5225
5226 case OBJTYPE_PLAYER:
5227 case OBJTYPE_GAME:
5228 return;
5229
5230 case NUM_OBJTYPES:
5231 break;
5232 }
5233
5234 log_error("%s(): Unhandled page object type %s (nb %d).", __FUNCTION__,
5235 objtype_get_name(property_page_get_objtype(pp)),
5236 property_page_get_objtype(pp));
5237 }
5238
5239 /****************************************************************************
5240 Set the column value in the list store of the property page.
5241 Returns TRUE if data was enetered into the store.
5242
5243 NB: This must match the conversion in objprop_get_gtype.
5244 ****************************************************************************/
property_page_set_store_value(struct property_page * pp,struct objprop * op,struct objbind * ob,GtkTreeIter * iter)5245 static bool property_page_set_store_value(struct property_page *pp,
5246 struct objprop *op,
5247 struct objbind *ob,
5248 GtkTreeIter *iter)
5249 {
5250 int col_id;
5251 struct propval *pv;
5252 enum value_types valtype;
5253 char buf[128], *p;
5254 GdkPixbuf *pixbuf = NULL;
5255 GtkListStore *store;
5256 gchar *buf2;
5257
5258 if (!pp || !pp->object_store || !op || !ob) {
5259 return FALSE;
5260 }
5261
5262 if (!objprop_show_in_listview(op)) {
5263 return FALSE;
5264 }
5265
5266 col_id = objprop_get_column_id(op);
5267 if (col_id < 0) {
5268 return FALSE;
5269 }
5270
5271 pv = objbind_get_value_from_object(ob, op);
5272 if (!pv) {
5273 return FALSE;
5274 }
5275
5276 valtype = objprop_get_valtype(op);
5277 store = pp->object_store;
5278
5279 switch (valtype) {
5280 case VALTYPE_NONE:
5281 break;
5282 case VALTYPE_INT:
5283 gtk_list_store_set(store, iter, col_id, pv->data.v_int, -1);
5284 break;
5285 case VALTYPE_BOOL:
5286 /* Set as translated string, not as untranslated G_TYPE_BOOLEAN */
5287 gtk_list_store_set(store, iter, col_id, propval_as_string(pv), -1);
5288 break;
5289 case VALTYPE_STRING:
5290 if (fc_strlcpy(buf, pv->data.v_string, 28) >= 28) {
5291 sz_strlcat(buf, "...");
5292 }
5293 for (p = buf; *p; p++) {
5294 if (*p == '\n' || *p == '\t' || *p == '\r') {
5295 *p = ' ';
5296 }
5297 }
5298 gtk_list_store_set(store, iter, col_id, buf, -1);
5299 break;
5300 case VALTYPE_PIXBUF:
5301 gtk_list_store_set(store, iter, col_id, pv->data.v_pixbuf, -1);
5302 break;
5303 case VALTYPE_BUILT_ARRAY:
5304 case VALTYPE_INVENTIONS_ARRAY:
5305 case VALTYPE_BV_SPECIAL:
5306 case VALTYPE_BV_ROADS:
5307 case VALTYPE_BV_BASES:
5308 case VALTYPE_NATION_HASH:
5309 buf2 = propval_as_string(pv);
5310 gtk_list_store_set(store, iter, col_id, buf2, -1);
5311 g_free(buf2);
5312 break;
5313 case VALTYPE_NATION:
5314 pixbuf = get_flag(pv->data.v_nation);
5315 gtk_list_store_set(store, iter, col_id, pixbuf, -1);
5316 if (pixbuf) {
5317 g_object_unref(pixbuf);
5318 }
5319 break;
5320 case VALTYPE_GOV:
5321 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
5322 gtk_list_store_set(store, iter, col_id, pixbuf, -1);
5323 if (pixbuf) {
5324 g_object_unref(pixbuf);
5325 }
5326 break;
5327 case VALTYPE_TILE_VISION_DATA:
5328 break;
5329 }
5330
5331 propval_free(pv);
5332
5333 return TRUE;
5334 }
5335
5336 /****************************************************************************
5337 Inserts any objbinds owned by this proprety page into the page's list
5338 store if they are not there already and refreshes all property widgets.
5339 ****************************************************************************/
property_page_fill_widgets(struct property_page * pp)5340 static void property_page_fill_widgets(struct property_page *pp)
5341 {
5342 struct objbind *focused;
5343
5344 if (!pp || !pp->objbind_table) {
5345 return;
5346 }
5347
5348 if (pp->object_store) {
5349 GtkTreeIter iter;
5350 GtkTreeRowReference *rr;
5351 GtkTreeModel *model;
5352 GtkTreePath *path;
5353
5354 model = GTK_TREE_MODEL(pp->object_store);
5355
5356 property_page_objbind_iterate(pp, ob) {
5357 if (objbind_get_rowref(ob)) {
5358 continue;
5359 }
5360 gtk_list_store_append(pp->object_store, &iter);
5361 gtk_list_store_set(pp->object_store, &iter, 0, ob, -1);
5362 path = gtk_tree_model_get_path(model, &iter);
5363 rr = gtk_tree_row_reference_new(model, path);
5364 gtk_tree_path_free(path);
5365 objbind_set_rowref(ob, rr);
5366
5367 property_page_objprop_iterate(pp, op) {
5368 property_page_set_store_value(pp, op, ob, &iter);
5369 } property_page_objprop_iterate_end;
5370 } property_page_objbind_iterate_end;
5371
5372 if (gtk_tree_model_get_iter_first(model, &iter)) {
5373 GtkTreeSelection *sel;
5374 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5375 gtk_tree_selection_select_iter(sel, &iter);
5376 }
5377 }
5378
5379 focused = property_page_get_focused_objbind(pp);
5380 property_page_objprop_iterate(pp, op) {
5381 objprop_refresh_widget(op, focused);
5382 } property_page_objprop_iterate_end;
5383 }
5384
5385 /****************************************************************************
5386 Get the objbind corresponding to the object that is currently in view
5387 (i.e. in the information/properties panels) or NULL if none.
5388 ****************************************************************************/
property_page_get_focused_objbind(struct property_page * pp)5389 static struct objbind *property_page_get_focused_objbind(struct property_page *pp)
5390 {
5391 if (!pp) {
5392 return NULL;
5393 }
5394 return pp->focused_objbind;
5395 }
5396
5397 /****************************************************************************
5398 Set the objbind that should be shown in the properties panel. Does not
5399 refresh property widgets.
5400 ****************************************************************************/
property_page_set_focused_objbind(struct property_page * pp,struct objbind * ob)5401 static void property_page_set_focused_objbind(struct property_page *pp,
5402 struct objbind *ob)
5403 {
5404 if (!pp) {
5405 return;
5406 }
5407 pp->focused_objbind = ob;
5408 }
5409
5410 /****************************************************************************
5411 Returns the objbind whose object corresponds to the given id, or NULL
5412 if no such objbind exists.
5413 ****************************************************************************/
property_page_get_objbind(struct property_page * pp,int object_id)5414 static struct objbind *property_page_get_objbind(struct property_page *pp,
5415 int object_id)
5416 {
5417 struct objbind *ob;
5418
5419 if (!pp || !pp->objbind_table) {
5420 return NULL;
5421 }
5422
5423 objbind_hash_lookup(pp->objbind_table, object_id, &ob);
5424 return ob;
5425 }
5426
5427 /****************************************************************************
5428 Removes all of the current objbinds and extracts new ones from the
5429 supplied list of tiles.
5430 ****************************************************************************/
property_page_load_tiles(struct property_page * pp,const struct tile_list * tiles)5431 static void property_page_load_tiles(struct property_page *pp,
5432 const struct tile_list *tiles)
5433 {
5434 if (!pp || !tiles) {
5435 return;
5436 }
5437
5438 tile_list_iterate(tiles, ptile) {
5439 property_page_add_objbinds_from_tile(pp, ptile);
5440 } tile_list_iterate_end;
5441
5442 property_page_fill_widgets(pp);
5443 }
5444
5445 /****************************************************************************
5446 Return the number of current bound objects to this property page.
5447 ****************************************************************************/
property_page_get_num_objbinds(const struct property_page * pp)5448 static int property_page_get_num_objbinds(const struct property_page *pp)
5449 {
5450 if (!pp || !pp->objbind_table) {
5451 return 0;
5452 }
5453 return objbind_hash_size(pp->objbind_table);
5454 }
5455
5456 /****************************************************************************
5457 Called when a user sets a new value for the given property via the GUI.
5458 Refreshes the properties widget if anything changes.
5459 ****************************************************************************/
property_page_change_value(struct property_page * pp,struct objprop * op,struct propval * pv)5460 static void property_page_change_value(struct property_page *pp,
5461 struct objprop *op,
5462 struct propval *pv)
5463 {
5464 GtkTreeSelection *sel;
5465 GtkTreeModel *model;
5466 GList *rows, *p;
5467 GtkTreePath *path;
5468 GtkTreeIter iter;
5469 struct objbind *ob;
5470
5471 if (!pp || !op || !pp->object_view) {
5472 return;
5473 }
5474
5475 if (objprop_is_readonly(op)) {
5476 return;
5477 }
5478
5479 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5480 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5481
5482 for (p = rows; p != NULL; p = p->next) {
5483 path = p->data;
5484 if (gtk_tree_model_get_iter(model, &iter, path)) {
5485 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5486 objbind_set_modified_value(ob, op, pv);
5487 }
5488 gtk_tree_path_free(path);
5489 }
5490 g_list_free(rows);
5491
5492 ob = property_page_get_focused_objbind(pp);
5493 objprop_refresh_widget(op, ob);
5494 }
5495
5496 /****************************************************************************
5497 Send all modified values of all selected properties.
5498 ****************************************************************************/
property_page_send_values(struct property_page * pp)5499 static void property_page_send_values(struct property_page *pp)
5500 {
5501 GtkTreeSelection *sel;
5502 GtkTreeModel *model;
5503 GList *rows, *p;
5504 GtkTreePath *path;
5505 GtkTreeIter iter;
5506 struct objbind *ob;
5507 union packetdata packet;
5508 struct connection *my_conn = &client.conn;
5509
5510 if (!pp || !pp->object_view) {
5511 return;
5512 }
5513
5514 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5515 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5516 return;
5517 }
5518
5519 packet = property_page_new_packet(pp);
5520 if (!packet.pointers.v_pointer1) {
5521 return;
5522 }
5523
5524 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5525 connection_do_buffer(my_conn);
5526 for (p = rows; p != NULL; p = p->next) {
5527 path = p->data;
5528 if (gtk_tree_model_get_iter(model, &iter, path)) {
5529 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5530 if (objbind_has_modified_properties(ob)) {
5531 objbind_pack_current_values(ob, packet);
5532 property_page_objprop_iterate(pp, op) {
5533 if (objprop_is_readonly(op)) {
5534 continue;
5535 }
5536 objbind_pack_modified_value(ob, op, packet);
5537 } property_page_objprop_iterate_end;
5538 property_page_send_packet(pp, packet);
5539 }
5540 }
5541 gtk_tree_path_free(path);
5542 }
5543 connection_do_unbuffer(my_conn);
5544 g_list_free(rows);
5545
5546 property_page_free_packet(pp, packet);
5547 }
5548
5549 /****************************************************************************
5550 Returns pointer to a packet suitable for this page's object type. Result
5551 should be freed using property_page_free_packet when no longer needed.
5552 ****************************************************************************/
property_page_new_packet(struct property_page * pp)5553 static union packetdata property_page_new_packet(struct property_page *pp)
5554 {
5555 union packetdata packet;
5556
5557 packet.pointers.v_pointer2 = NULL;
5558
5559 if (!pp) {
5560 packet.pointers.v_pointer1 = NULL;
5561 return packet;
5562 }
5563
5564 switch (property_page_get_objtype(pp)) {
5565 case OBJTYPE_TILE:
5566 packet.tile = fc_calloc(1, sizeof(*packet.tile));
5567 break;
5568 case OBJTYPE_STARTPOS:
5569 packet.startpos = fc_calloc(1, sizeof(*packet.startpos));
5570 break;
5571 case OBJTYPE_UNIT:
5572 packet.unit = fc_calloc(1, sizeof(*packet.unit));
5573 break;
5574 case OBJTYPE_CITY:
5575 packet.city = fc_calloc(1, sizeof(*packet.city));
5576 break;
5577 case OBJTYPE_PLAYER:
5578 packet.player = fc_calloc(1, sizeof(*packet.player));
5579 break;
5580 case OBJTYPE_GAME:
5581 packet.game.game = fc_calloc(1, sizeof(*packet.game.game));
5582 packet.game.desc = fc_calloc(1, sizeof(*packet.game.desc));
5583 break;
5584 case NUM_OBJTYPES:
5585 break;
5586 }
5587
5588 return packet;
5589 }
5590
5591 /****************************************************************************
5592 Sends the given packet.
5593 ****************************************************************************/
property_page_send_packet(struct property_page * pp,union packetdata packet)5594 static void property_page_send_packet(struct property_page *pp,
5595 union packetdata packet)
5596 {
5597 struct connection *my_conn = &client.conn;
5598
5599 if (!pp || !packet.pointers.v_pointer1) {
5600 return;
5601 }
5602
5603 switch (property_page_get_objtype(pp)) {
5604 case OBJTYPE_TILE:
5605 send_packet_edit_tile(my_conn, packet.tile);
5606 return;
5607 case OBJTYPE_STARTPOS:
5608 send_packet_edit_startpos_full(my_conn, packet.startpos);
5609 return;
5610 case OBJTYPE_UNIT:
5611 send_packet_edit_unit(my_conn, packet.unit);
5612 return;
5613 case OBJTYPE_CITY:
5614 send_packet_edit_city(my_conn, packet.city);
5615 return;
5616 case OBJTYPE_PLAYER:
5617 send_packet_edit_player(my_conn, packet.player);
5618 return;
5619 case OBJTYPE_GAME:
5620 send_packet_edit_game(my_conn, packet.game.game);
5621 send_packet_edit_scenario_desc(my_conn, packet.game.desc);
5622 return;
5623 case NUM_OBJTYPES:
5624 break;
5625 }
5626
5627 log_error("%s(): Unhandled object type %s (nb %d).",
5628 __FUNCTION__, objtype_get_name(property_page_get_objtype(pp)),
5629 property_page_get_objtype(pp));
5630 }
5631
5632 /****************************************************************************
5633 Free any resources being used by the packet.
5634 ****************************************************************************/
property_page_free_packet(struct property_page * pp,union packetdata packet)5635 static void property_page_free_packet(struct property_page *pp,
5636 union packetdata packet)
5637 {
5638 if (!packet.pointers.v_pointer1) {
5639 return;
5640 }
5641
5642 free(packet.pointers.v_pointer1);
5643 packet.pointers.v_pointer1 = NULL;
5644
5645 if (packet.pointers.v_pointer2 != NULL) {
5646 free(packet.pointers.v_pointer2);
5647 packet.pointers.v_pointer2 = NULL;
5648 }
5649 }
5650
5651 /****************************************************************************
5652 Reload the displayed values of all properties for the selected bound
5653 objects. Hence, deletes all their stored modified values.
5654 ****************************************************************************/
property_page_reset_objbinds(struct property_page * pp)5655 static void property_page_reset_objbinds(struct property_page *pp)
5656 {
5657 GtkTreeSelection *sel;
5658 GtkTreeModel *model;
5659 GtkTreeIter iter;
5660 GtkTreePath *path;
5661 GList *rows, *p;
5662 struct objbind *ob;
5663
5664 if (!pp || !pp->object_view) {
5665 return;
5666 }
5667
5668 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5669 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5670 return;
5671 }
5672
5673 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5674 for (p = rows; p != NULL; p = p->next) {
5675 path = p->data;
5676 if (gtk_tree_model_get_iter(model, &iter, path)) {
5677 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5678 objbind_clear_all_modified_values(ob);
5679 property_page_objprop_iterate(pp, op) {
5680 property_page_set_store_value(pp, op, ob, &iter);
5681 } property_page_objprop_iterate_end;
5682 }
5683 gtk_tree_path_free(path);
5684 }
5685 g_list_free(rows);
5686
5687 ob = property_page_get_focused_objbind(pp);
5688 property_page_objprop_iterate(pp, op) {
5689 objprop_refresh_widget(op, ob);
5690 } property_page_objprop_iterate_end;
5691 }
5692
5693 /****************************************************************************
5694 Destroy all selected objects in the current property page.
5695 ****************************************************************************/
property_page_destroy_objects(struct property_page * pp)5696 static void property_page_destroy_objects(struct property_page *pp)
5697 {
5698 GtkTreeSelection *sel;
5699 GtkTreeModel *model;
5700 GtkTreeIter iter;
5701 GtkTreePath *path;
5702 GList *rows, *p;
5703 struct objbind *ob;
5704 struct connection *my_conn = &client.conn;
5705
5706 if (!pp || !pp->object_view) {
5707 return;
5708 }
5709
5710 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5711 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5712 return;
5713 }
5714
5715 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5716 connection_do_buffer(my_conn);
5717 for (p = rows; p != NULL; p = p->next) {
5718 path = p->data;
5719 if (gtk_tree_model_get_iter(model, &iter, path)) {
5720 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5721 objbind_request_destroy_object(ob);
5722 }
5723 gtk_tree_path_free(path);
5724 }
5725 connection_do_unbuffer(my_conn);
5726 g_list_free(rows);
5727 }
5728
5729 /****************************************************************************
5730 Create objects corresponding to the type of this property page. Parameters
5731 such as the type, count, size and player owner are taken from the current
5732 editor state. The 'hint_tiles' argument is a list of tiles where the
5733 objects could be created.
5734 ****************************************************************************/
property_page_create_objects(struct property_page * pp,struct tile_list * hint_tiles)5735 static void property_page_create_objects(struct property_page *pp,
5736 struct tile_list *hint_tiles)
5737 {
5738 enum editor_object_type objtype;
5739 int apno, value, count, size;
5740 int tag;
5741 struct connection *my_conn = &client.conn;
5742 struct tile *ptile = NULL;
5743 struct player *pplayer;
5744
5745 if (!pp) {
5746 return;
5747 }
5748
5749 objtype = property_page_get_objtype(pp);
5750 if (objtype_is_conserved(objtype)) {
5751 return;
5752 }
5753
5754 tag = get_next_unique_tag();
5755 count = 1;
5756
5757 switch (objtype) {
5758 case OBJTYPE_STARTPOS:
5759 if (hint_tiles) {
5760 tile_list_iterate(hint_tiles, atile) {
5761 if (NULL == map_startpos_get(atile)) {
5762 ptile = atile;
5763 break;
5764 }
5765 } tile_list_iterate_end;
5766 }
5767
5768 if (NULL == ptile) {
5769 ptile = get_center_tile_mapcanvas();
5770 }
5771
5772 if (NULL == ptile) {
5773 break;
5774 }
5775
5776 dsend_packet_edit_startpos(my_conn, tile_index(ptile), FALSE, tag);
5777 break;
5778
5779 case OBJTYPE_UNIT:
5780 if (hint_tiles) {
5781 tile_list_iterate(hint_tiles, atile) {
5782 if (can_create_unit_at_tile(atile)) {
5783 ptile = atile;
5784 break;
5785 }
5786 } tile_list_iterate_end;
5787 }
5788
5789 if (!ptile) {
5790 struct unit *punit;
5791 property_page_objbind_iterate(pp, ob) {
5792 punit = objbind_get_object(ob);
5793 if (punit && can_create_unit_at_tile(unit_tile(punit))) {
5794 ptile = unit_tile(punit);
5795 break;
5796 }
5797 } property_page_objbind_iterate_end;
5798 }
5799
5800 if (!ptile) {
5801 ptile = get_center_tile_mapcanvas();
5802 }
5803
5804 if (!ptile) {
5805 break;
5806 }
5807
5808 apno = editor_tool_get_applied_player(ETT_UNIT);
5809 count = editor_tool_get_count(ETT_UNIT);
5810 value = editor_tool_get_value(ETT_UNIT);
5811 dsend_packet_edit_unit_create(my_conn, apno, tile_index(ptile),
5812 value, count, tag);
5813 break;
5814
5815 case OBJTYPE_CITY:
5816 apno = editor_tool_get_applied_player(ETT_CITY);
5817 pplayer = player_by_number(apno);
5818 if (pplayer && hint_tiles) {
5819 tile_list_iterate(hint_tiles, atile) {
5820 if (!is_enemy_unit_tile(atile, pplayer)
5821 && city_can_be_built_here(atile, NULL)) {
5822 ptile = atile;
5823 break;
5824 }
5825 } tile_list_iterate_end;
5826 }
5827
5828 if (!ptile) {
5829 ptile = get_center_tile_mapcanvas();
5830 }
5831
5832 if (!ptile) {
5833 break;
5834 }
5835
5836 size = editor_tool_get_size(ETT_CITY);
5837 dsend_packet_edit_city_create(my_conn, apno, tile_index(ptile),
5838 size, tag);
5839 break;
5840
5841 case OBJTYPE_PLAYER:
5842 dsend_packet_edit_player_create(my_conn, tag);
5843 break;
5844
5845 case OBJTYPE_TILE:
5846 case OBJTYPE_GAME:
5847 case NUM_OBJTYPES:
5848 break;
5849 }
5850
5851 property_page_store_creation_tag(pp, tag, count);
5852 }
5853
5854 /****************************************************************************
5855 Update objbinds and widgets according to how the object given by
5856 'object_id' has changed. If the object no longer exists then the
5857 objbind is removed from the property page.
5858 ****************************************************************************/
property_page_object_changed(struct property_page * pp,int object_id,bool removed)5859 static void property_page_object_changed(struct property_page *pp,
5860 int object_id,
5861 bool removed)
5862 {
5863 struct objbind *ob;
5864 GtkTreeRowReference *rr;
5865
5866 ob = property_page_get_objbind(pp, object_id);
5867 if (!ob) {
5868 return;
5869 }
5870
5871 rr = objbind_get_rowref(ob);
5872 if (rr && gtk_tree_row_reference_valid(rr)) {
5873 GtkTreePath *path;
5874 GtkTreeIter iter;
5875 GtkTreeModel *model;
5876
5877 model = GTK_TREE_MODEL(pp->object_store);
5878 path = gtk_tree_row_reference_get_path(rr);
5879
5880 if (gtk_tree_model_get_iter(model, &iter, path)) {
5881 if (removed) {
5882 gtk_list_store_remove(pp->object_store, &iter);
5883 } else {
5884 property_page_objprop_iterate(pp, op) {
5885 property_page_set_store_value(pp, op, ob, &iter);
5886 } property_page_objprop_iterate_end;
5887 }
5888 }
5889
5890 gtk_tree_path_free(path);
5891 }
5892
5893 if (removed) {
5894 objbind_hash_remove(pp->objbind_table, object_id);
5895 return;
5896 }
5897
5898 if (ob == property_page_get_focused_objbind(pp)) {
5899 property_page_objprop_iterate(pp, op) {
5900 objprop_refresh_widget(op, ob);
5901 } property_page_objprop_iterate_end;
5902 }
5903 }
5904
5905 /****************************************************************************
5906 Handle a notification of object creation sent back from the server. If
5907 this is something we previously requested, then 'tag' should be found in
5908 the tag table. In this case we create a new objbind for the object given
5909 by 'object_id' and add it to this page.
5910 ****************************************************************************/
property_page_object_created(struct property_page * pp,int tag,int object_id)5911 static void property_page_object_created(struct property_page *pp,
5912 int tag, int object_id)
5913 {
5914 gpointer object;
5915 enum editor_object_type objtype;
5916
5917 if (!property_page_tag_is_known(pp, tag)) {
5918 return;
5919 }
5920 property_page_remove_creation_tag(pp, tag);
5921
5922 objtype = property_page_get_objtype(pp);
5923 object = objtype_get_object_from_id(objtype, object_id);
5924
5925 if (!object) {
5926 return;
5927 }
5928
5929 property_page_add_objbind(pp, object);
5930 property_page_fill_widgets(pp);
5931 }
5932
5933 /****************************************************************************
5934 Add the extviewer's view widget to the property page so that it can
5935 be shown in the extended property view panel.
5936 ****************************************************************************/
property_page_add_extviewer(struct property_page * pp,struct extviewer * ev)5937 static void property_page_add_extviewer(struct property_page *pp,
5938 struct extviewer *ev)
5939 {
5940 GtkWidget *w;
5941
5942 if (!pp || !ev) {
5943 return;
5944 }
5945
5946 w = extviewer_get_view_widget(ev);
5947 if (!w) {
5948 return;
5949 }
5950 gtk_notebook_append_page(GTK_NOTEBOOK(pp->extviewer_notebook), w, NULL);
5951 }
5952
5953 /****************************************************************************
5954 Make the given extended property viewer's view widget visible in the
5955 property page.
5956 ****************************************************************************/
property_page_show_extviewer(struct property_page * pp,struct extviewer * ev)5957 static void property_page_show_extviewer(struct property_page *pp,
5958 struct extviewer *ev)
5959 {
5960 GtkWidget *w;
5961 GtkNotebook *notebook;
5962 int page;
5963
5964 if (!pp || !ev) {
5965 return;
5966 }
5967
5968 w = extviewer_get_view_widget(ev);
5969 if (!w) {
5970 return;
5971 }
5972
5973 notebook = GTK_NOTEBOOK(pp->extviewer_notebook);
5974 page = gtk_notebook_page_num(notebook, w);
5975 gtk_notebook_set_current_page(notebook, page);
5976 }
5977
5978 /****************************************************************************
5979 Store the given object creation tag so that when the server notifies
5980 us about it we know what to do, up to 'count' times.
5981 ****************************************************************************/
property_page_store_creation_tag(struct property_page * pp,int tag,int count)5982 static void property_page_store_creation_tag(struct property_page *pp,
5983 int tag, int count)
5984 {
5985 if (!pp || !pp->tag_table) {
5986 return;
5987 }
5988
5989 if (stored_tag_hash_lookup(pp->tag_table, tag, NULL)) {
5990 log_error("Attempted to insert object creation tag %d "
5991 "twice into tag table for property page %p (%d %s).",
5992 tag, pp, property_page_get_objtype(pp),
5993 property_page_get_name(pp));
5994 return;
5995 }
5996
5997 stored_tag_hash_insert(pp->tag_table, tag, count);
5998 }
5999
6000 /****************************************************************************
6001 Decrease the tag count and remove the object creation tag if it is no
6002 longer needed.
6003 ****************************************************************************/
property_page_remove_creation_tag(struct property_page * pp,int tag)6004 static void property_page_remove_creation_tag(struct property_page *pp,
6005 int tag)
6006 {
6007 int count;
6008
6009 if (!pp || !pp->tag_table) {
6010 return;
6011 }
6012
6013 if (stored_tag_hash_lookup(pp->tag_table, tag, &count)) {
6014 if (0 >= --count) {
6015 stored_tag_hash_remove(pp->tag_table, tag);
6016 }
6017 }
6018 }
6019
6020 /****************************************************************************
6021 Check if the given tag is one that we previously stored.
6022 ****************************************************************************/
property_page_tag_is_known(struct property_page * pp,int tag)6023 static bool property_page_tag_is_known(struct property_page *pp, int tag)
6024 {
6025 if (!pp || !pp->tag_table) {
6026 return FALSE;
6027 }
6028 return stored_tag_hash_lookup(pp->tag_table, tag, NULL);
6029 }
6030
6031 /****************************************************************************
6032 Remove all tags in the tag table.
6033 ****************************************************************************/
property_page_clear_tags(struct property_page * pp)6034 static void property_page_clear_tags(struct property_page *pp)
6035 {
6036 if (!pp || !pp->tag_table) {
6037 return;
6038 }
6039 stored_tag_hash_clear(pp->tag_table);
6040 }
6041
6042 /****************************************************************************
6043 Handles the 'clicked' signal for the "Apply" button in the property page.
6044 ****************************************************************************/
property_page_apply_button_clicked(GtkButton * button,gpointer userdata)6045 static void property_page_apply_button_clicked(GtkButton *button,
6046 gpointer userdata)
6047 {
6048 struct property_page *pp = userdata;
6049 property_page_send_values(pp);
6050 }
6051
6052 /****************************************************************************
6053 Handles the 'clicked' signal for the "Refresh" button in the
6054 property page.
6055 ****************************************************************************/
property_page_refresh_button_clicked(GtkButton * button,gpointer userdata)6056 static void property_page_refresh_button_clicked(GtkButton *button,
6057 gpointer userdata)
6058 {
6059 struct property_page *pp = userdata;
6060 property_page_reset_objbinds(pp);
6061 }
6062
6063 /****************************************************************************
6064 Handle a request to create a new object in the property page.
6065 ****************************************************************************/
property_page_create_button_clicked(GtkButton * button,gpointer userdata)6066 static void property_page_create_button_clicked(GtkButton *button,
6067 gpointer userdata)
6068 {
6069 struct property_page *pp = userdata, *tile_pp;
6070 struct tile_list *tiles = NULL;
6071 struct tile *ptile;
6072
6073 if (!pp) {
6074 return;
6075 }
6076
6077 tile_pp = property_editor_get_page(pp->pe_parent, OBJTYPE_TILE);
6078 tiles = tile_list_new();
6079
6080 property_page_objbind_iterate(tile_pp, ob) {
6081 ptile = objbind_get_object(ob);
6082 if (ptile) {
6083 tile_list_append(tiles, ptile);
6084 }
6085 } property_page_objbind_iterate_end;
6086
6087 property_page_create_objects(pp, tiles);
6088 tile_list_destroy(tiles);
6089 }
6090
6091 /****************************************************************************
6092 Handle a click on the "destroy" button.
6093 ****************************************************************************/
property_page_destroy_button_clicked(GtkButton * button,gpointer userdata)6094 static void property_page_destroy_button_clicked(GtkButton *button,
6095 gpointer userdata)
6096 {
6097 struct property_page *pp = userdata;
6098 property_page_destroy_objects(pp);
6099 }
6100
6101 /****************************************************************************
6102 Create and add a property page for the given object type
6103 to the property editor. Returns TRUE if successful.
6104 ****************************************************************************/
property_editor_add_page(struct property_editor * pe,enum editor_object_type objtype)6105 static bool property_editor_add_page(struct property_editor *pe,
6106 enum editor_object_type objtype)
6107 {
6108 struct property_page *pp;
6109 GtkWidget *label;
6110 const char *name;
6111
6112 if (!pe || !pe->notebook) {
6113 return FALSE;
6114 }
6115
6116 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
6117 return FALSE;
6118 }
6119
6120 pp = property_page_new(objtype, pe);
6121 if (!pp) {
6122 return FALSE;
6123 }
6124
6125 name = property_page_get_name(pp);
6126 label = gtk_label_new(name);
6127 gtk_notebook_append_page(GTK_NOTEBOOK(pe->notebook),
6128 pp->widget, label);
6129
6130 pe->property_pages[objtype] = pp;
6131
6132 return TRUE;
6133 }
6134
6135 /****************************************************************************
6136 Returns the property page for the given object type.
6137 ****************************************************************************/
6138 static struct property_page *
property_editor_get_page(struct property_editor * pe,enum editor_object_type objtype)6139 property_editor_get_page(struct property_editor *pe,
6140 enum editor_object_type objtype)
6141 {
6142 if (!pe || !(0 <= objtype && objtype < NUM_OBJTYPES)) {
6143 return NULL;
6144 }
6145
6146 return pe->property_pages[objtype];
6147 }
6148
6149 /****************************************************************************
6150 Create and return the property editor widget bundle.
6151 ****************************************************************************/
property_editor_new(void)6152 static struct property_editor *property_editor_new(void)
6153 {
6154 struct property_editor *pe;
6155 GtkWidget *win, *notebook, *vbox;
6156 enum editor_object_type objtype;
6157
6158 pe = fc_calloc(1, sizeof(*pe));
6159
6160 /* The property editor dialog window. */
6161
6162 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6163 gtk_window_set_title(GTK_WINDOW(win), _("Property Editor"));
6164 gtk_window_set_resizable(GTK_WINDOW(win), TRUE);
6165 gtk_window_set_default_size(GTK_WINDOW(win), 780, 560);
6166 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
6167 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(toplevel));
6168 gtk_window_set_destroy_with_parent(GTK_WINDOW(win), TRUE);
6169 gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG);
6170 gtk_container_set_border_width(GTK_CONTAINER(win), 4);
6171 g_signal_connect(win, "delete-event",
6172 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
6173 pe->widget = win;
6174
6175 vbox = gtk_grid_new();
6176 gtk_container_add(GTK_CONTAINER(win), vbox);
6177
6178 /* Property pages. */
6179
6180 notebook = gtk_notebook_new();
6181 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), TRUE);
6182 gtk_container_add(GTK_CONTAINER(vbox), notebook);
6183 pe->notebook = notebook;
6184
6185 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6186 property_editor_add_page(pe, objtype);
6187 }
6188
6189 return pe;
6190 }
6191
6192 /****************************************************************************
6193 Get the property editor for the client's GUI.
6194 ****************************************************************************/
editprop_get_property_editor(void)6195 struct property_editor *editprop_get_property_editor(void)
6196 {
6197 if (!the_property_editor) {
6198 the_property_editor = property_editor_new();
6199 }
6200 return the_property_editor;
6201 }
6202
6203 /****************************************************************************
6204 Refresh the given property editor according to the given list of tiles.
6205 ****************************************************************************/
property_editor_load_tiles(struct property_editor * pe,const struct tile_list * tiles)6206 void property_editor_load_tiles(struct property_editor *pe,
6207 const struct tile_list *tiles)
6208 {
6209 struct property_page *pp;
6210 enum editor_object_type objtype;
6211 int i;
6212 const enum editor_object_type preferred[] = {
6213 OBJTYPE_CITY,
6214 OBJTYPE_UNIT,
6215 OBJTYPE_STARTPOS,
6216 OBJTYPE_TILE
6217 };
6218
6219 if (!pe || !tiles) {
6220 return;
6221 }
6222
6223 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6224 pp = property_editor_get_page(pe, objtype);
6225 property_page_load_tiles(pp, tiles);
6226 }
6227
6228 for (i = 0; i < ARRAY_SIZE(preferred) - 1; i++) {
6229 pp = property_editor_get_page(pe, preferred[i]);
6230 if (property_page_get_num_objbinds(pp) > 0) {
6231 break;
6232 }
6233 }
6234 objtype = preferred[i];
6235 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6236 }
6237
6238 /****************************************************************************
6239 Show the property editor to the user, with given page corresponding to
6240 'objtype' in front (if a valid object type).
6241 ****************************************************************************/
property_editor_popup(struct property_editor * pe,enum editor_object_type objtype)6242 void property_editor_popup(struct property_editor *pe,
6243 enum editor_object_type objtype)
6244 {
6245 if (!pe || !pe->widget) {
6246 return;
6247 }
6248
6249 gtk_widget_show_all(pe->widget);
6250
6251 gtk_window_present(GTK_WINDOW(pe->widget));
6252 if (0 <= objtype && objtype < NUM_OBJTYPES) {
6253 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6254 }
6255 }
6256
6257 /****************************************************************************
6258 Hide the property editor window.
6259 ****************************************************************************/
property_editor_popdown(struct property_editor * pe)6260 void property_editor_popdown(struct property_editor *pe)
6261 {
6262 if (!pe || !pe->widget) {
6263 return;
6264 }
6265 gtk_widget_hide(pe->widget);
6266 }
6267
6268 /****************************************************************************
6269 Handle a notification from the client core that some object has changed
6270 state at the server side (including being removed).
6271 ****************************************************************************/
property_editor_handle_object_changed(struct property_editor * pe,enum editor_object_type objtype,int object_id,bool remove)6272 void property_editor_handle_object_changed(struct property_editor *pe,
6273 enum editor_object_type objtype,
6274 int object_id,
6275 bool remove)
6276 {
6277 struct property_page *pp;
6278
6279 if (!pe) {
6280 return;
6281 }
6282
6283 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
6284 return;
6285 }
6286
6287 pp = property_editor_get_page(pe, objtype);
6288 property_page_object_changed(pp, object_id, remove);
6289 }
6290
6291 /****************************************************************************
6292 Handle a notification that an object was created under the given tag.
6293 ****************************************************************************/
property_editor_handle_object_created(struct property_editor * pe,int tag,int object_id)6294 void property_editor_handle_object_created(struct property_editor *pe,
6295 int tag, int object_id)
6296 {
6297 enum editor_object_type objtype;
6298 struct property_page *pp;
6299
6300 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6301 if (objtype_is_conserved(objtype)) {
6302 continue;
6303 }
6304 pp = property_editor_get_page(pe, objtype);
6305 property_page_object_created(pp, tag, object_id);
6306 }
6307 }
6308
6309 /****************************************************************************
6310 Clear all property pages in the given property editor.
6311 ****************************************************************************/
property_editor_clear(struct property_editor * pe)6312 void property_editor_clear(struct property_editor *pe)
6313 {
6314 enum editor_object_type objtype;
6315 struct property_page *pp;
6316
6317 if (!pe) {
6318 return;
6319 }
6320
6321 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6322 pp = property_editor_get_page(pe, objtype);
6323 property_page_clear_objbinds(pp);
6324 property_page_clear_tags(pp);
6325 }
6326 }
6327
6328 /****************************************************************************
6329 Clear and load objects into the property page corresponding to the given
6330 object type. Also, make it the current shown notebook page.
6331 ****************************************************************************/
property_editor_reload(struct property_editor * pe,enum editor_object_type objtype)6332 void property_editor_reload(struct property_editor *pe,
6333 enum editor_object_type objtype)
6334 {
6335 struct property_page *pp;
6336
6337 if (!pe) {
6338 return;
6339 }
6340
6341 pp = property_editor_get_page(pe, objtype);
6342 if (!pp) {
6343 return;
6344 }
6345
6346 property_page_clear_objbinds(pp);
6347
6348 switch (objtype) {
6349 case OBJTYPE_PLAYER:
6350 players_iterate(pplayer) {
6351 property_page_add_objbind(pp, pplayer);
6352 } players_iterate_end;
6353 break;
6354 case OBJTYPE_GAME:
6355 property_page_add_objbind(pp, &game);
6356 break;
6357 case OBJTYPE_TILE:
6358 case OBJTYPE_STARTPOS:
6359 case OBJTYPE_UNIT:
6360 case OBJTYPE_CITY:
6361 case NUM_OBJTYPES:
6362 break;
6363 }
6364
6365 property_page_fill_widgets(pp);
6366 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6367 }
6368
6369 /****************************************************************************
6370 Create a new property filter from the given filter string. Result
6371 should be freed by property_filter_free when no longed needed.
6372
6373 The filter string is '|' ("or") separated list of '&' ("and") separated
6374 lists of patterns. A pattern may be preceeded by '!' to have its result
6375 negated.
6376
6377 NB: If you change the behaviour of this function, be sure to update
6378 the filter tooltip in property_page_new().
6379 ****************************************************************************/
property_filter_new(const char * filter)6380 static struct property_filter *property_filter_new(const char *filter)
6381 {
6382 struct property_filter *pf;
6383 struct pf_conjunction *pfc;
6384 struct pf_pattern *pfp;
6385 int or_clause_count, and_clause_count;
6386 char *or_clauses[PF_MAX_CLAUSES], *and_clauses[PF_MAX_CLAUSES];
6387 const char *pattern;
6388 int i, j;
6389
6390 pf = fc_calloc(1, sizeof(*pf));
6391
6392 if (!filter || filter[0] == '\0') {
6393 return pf;
6394 }
6395
6396 or_clause_count = get_tokens(filter, or_clauses,
6397 PF_MAX_CLAUSES,
6398 PF_DISJUNCTION_SEPARATOR);
6399
6400 for (i = 0; i < or_clause_count; i++) {
6401 if (or_clauses[i][0] == '\0') {
6402 continue;
6403 }
6404 pfc = &pf->disjunction[pf->count];
6405
6406 and_clause_count = get_tokens(or_clauses[i], and_clauses,
6407 PF_MAX_CLAUSES,
6408 PF_CONJUNCTION_SEPARATOR);
6409
6410 for (j = 0; j < and_clause_count; j++) {
6411 if (and_clauses[j][0] == '\0') {
6412 continue;
6413 }
6414 pfp = &pfc->conjunction[pfc->count];
6415 pattern = and_clauses[j];
6416
6417 switch (pattern[0]) {
6418 case '!':
6419 pfp->negate = TRUE;
6420 pfp->text = fc_strdup(pattern + 1);
6421 break;
6422 default:
6423 pfp->text = fc_strdup(pattern);
6424 break;
6425 }
6426 pfc->count++;
6427 }
6428 free_tokens(and_clauses, and_clause_count);
6429 pf->count++;
6430 }
6431
6432 free_tokens(or_clauses, or_clause_count);
6433
6434 return pf;
6435 }
6436
6437 /****************************************************************************
6438 Returns TRUE if the filter matches the given object property.
6439
6440 The filter matches if its truth value is TRUE. That is, it has at least
6441 one OR clause in which all AND clauses are TRUE. An AND clause is TRUE
6442 if its pattern matches the name of the given object property (case is
6443 ignored), or it is negated and does not match. For example:
6444
6445 a - Matches all properties whose names contain "a" (or "A").
6446 !a - Matches all properties whose names do not contain "a".
6447 a|b - Matches all properties whose names contain "a" or "b".
6448 a|b&c - Matches all properties whose names contain either an "a",
6449 or contain both "b" and "c".
6450
6451 NB: If you change the behaviour of this function, be sure to update
6452 the filter tooltip in property_page_new().
6453 ****************************************************************************/
property_filter_match(struct property_filter * pf,const struct objprop * op)6454 static bool property_filter_match(struct property_filter *pf,
6455 const struct objprop *op)
6456 {
6457 struct pf_pattern *pfp;
6458 struct pf_conjunction *pfc;
6459 const char *name;
6460 bool match, or_result, and_result;
6461 int i, j;
6462
6463 if (!pf) {
6464 return TRUE;
6465 }
6466 if (!op) {
6467 return FALSE;
6468 }
6469
6470 name = objprop_get_name(op);
6471 if (!name) {
6472 return FALSE;
6473 }
6474
6475 if (pf->count < 1) {
6476 return TRUE;
6477 }
6478
6479 or_result = FALSE;
6480
6481 for (i = 0; i < pf->count; i++) {
6482 pfc = &pf->disjunction[i];
6483 and_result = TRUE;
6484 for (j = 0; j < pfc->count; j++) {
6485 pfp = &pfc->conjunction[j];
6486 match = (pfp->text[0] == '\0'
6487 || fc_strcasestr(name, pfp->text));
6488 if (pfp->negate) {
6489 match = !match;
6490 }
6491 and_result = and_result && match;
6492 if (!and_result) {
6493 break;
6494 }
6495 }
6496 or_result = or_result || and_result;
6497 if (or_result) {
6498 break;
6499 }
6500 }
6501
6502 return or_result;
6503 }
6504
6505 /****************************************************************************
6506 Frees all memory used by the property filter.
6507 ****************************************************************************/
property_filter_free(struct property_filter * pf)6508 static void property_filter_free(struct property_filter *pf)
6509 {
6510 struct pf_pattern *pfp;
6511 struct pf_conjunction *pfc;
6512 int i, j;
6513
6514 if (!pf) {
6515 return;
6516 }
6517
6518 for (i = 0; i < pf->count; i++) {
6519 pfc = &pf->disjunction[i];
6520 for (j = 0; j < pfc->count; j++) {
6521 pfp = &pfc->conjunction[j];
6522 if (pfp->text != NULL) {
6523 free(pfp->text);
6524 pfp->text = NULL;
6525 }
6526 }
6527 pfc->count = 0;
6528 }
6529 pf->count = 0;
6530 free(pf);
6531 }
6532
6533 /****************************************************************************
6534 Returns a translated string name for the given "vision layer".
6535 ****************************************************************************/
vision_layer_get_name(enum vision_layer vl)6536 const char *vision_layer_get_name(enum vision_layer vl)
6537 {
6538 switch (vl) {
6539 case V_MAIN:
6540 /* TRANS: Vision layer name. Feel free to leave untranslated. */
6541 return _("Seen (Main)");
6542 case V_INVIS:
6543 /* TRANS: Vision layer name. Feel free to leave untranslated. */
6544 return _("Seen (Invis)");
6545 case V_COUNT:
6546 break;
6547 }
6548
6549 log_error("%s(): Unrecognized vision layer %d.", __FUNCTION__, vl);
6550 return _("Unknown");
6551 }
6552