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.22 */
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 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
3622 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
3623 } else {
3624 const bool editable = !objprop_is_readonly(op);
3625 view = gtk_text_view_new_with_buffer(textbuf);
3626 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), editable);
3627 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), editable);
3628 }
3629 gtk_widget_set_hexpand(view, TRUE);
3630 gtk_widget_set_vexpand(view, TRUE);
3631
3632 gtk_container_add(GTK_CONTAINER(scrollwin), view);
3633 }
3634
3635 switch (propid) {
3636
3637 case OPID_TILE_SPECIALS:
3638 case OPID_TILE_ROADS:
3639 case OPID_TILE_BASES:
3640 /* TRANS: As in "this tile special is present". */
3641 add_column(view, 0, _("Present"), G_TYPE_BOOLEAN, TRUE, FALSE,
3642 G_CALLBACK(extviewer_view_cell_toggled), ev);
3643 add_column(view, 1, _("ID"), G_TYPE_INT,
3644 FALSE, FALSE, NULL, NULL);
3645 add_column(view, 2, _("Name"), G_TYPE_STRING,
3646 FALSE, FALSE, NULL, NULL);
3647 break;
3648
3649 case OPID_TILE_VISION:
3650 add_column(view, 0, _("ID"), G_TYPE_INT,
3651 FALSE, FALSE, NULL, NULL);
3652 add_column(view, 1, _("Nation"), GDK_TYPE_PIXBUF,
3653 FALSE, FALSE, NULL, NULL);
3654 add_column(view, 2, _("Name"), G_TYPE_STRING,
3655 FALSE, FALSE, NULL, NULL);
3656 add_column(view, 3, _("Known"), G_TYPE_BOOLEAN,
3657 FALSE, FALSE, NULL, NULL);
3658 vision_layer_iterate(v) {
3659 add_column(view, 4 + v, vision_layer_get_name(v),
3660 G_TYPE_BOOLEAN, FALSE, FALSE, NULL, NULL);
3661 } vision_layer_iterate_end;
3662 break;
3663
3664 case OPID_CITY_BUILDINGS:
3665 /* TRANS: As in "this building is present". */
3666 add_column(view, 0, _("Present"), G_TYPE_BOOLEAN, TRUE, FALSE,
3667 G_CALLBACK(extviewer_view_cell_toggled), ev);
3668 add_column(view, 1, _("ID"), G_TYPE_INT,
3669 FALSE, FALSE, NULL, NULL);
3670 add_column(view, 2, _("Name"), G_TYPE_STRING,
3671 FALSE, FALSE, NULL, NULL);
3672 /* TRANS: As in "the turn when this building was built". */
3673 add_column(view, 3, _("Turn Built"), G_TYPE_STRING,
3674 FALSE, FALSE, NULL, NULL);
3675 break;
3676
3677 case OPID_STARTPOS_NATIONS:
3678 /* TRANS: As in "the player has set this nation". */
3679 add_column(view, 0, _("Set"), G_TYPE_BOOLEAN, TRUE, FALSE,
3680 G_CALLBACK(extviewer_view_cell_toggled), ev);
3681 add_column(view, 1, _("ID"), G_TYPE_INT,
3682 FALSE, FALSE, NULL, NULL);
3683 add_column(view, 2, _("Flag"), GDK_TYPE_PIXBUF,
3684 FALSE, FALSE, NULL, NULL);
3685 add_column(view, 3, _("Name"), G_TYPE_STRING,
3686 FALSE, FALSE, NULL, NULL);
3687 break;
3688
3689 case OPID_PLAYER_NATION:
3690 case OPID_PLAYER_GOV:
3691 /* TRANS: As in "the player has set this nation". */
3692 add_column(view, 0, _("Set"), G_TYPE_BOOLEAN, TRUE, TRUE,
3693 G_CALLBACK(extviewer_view_cell_toggled), ev);
3694 add_column(view, 1, _("ID"), G_TYPE_INT,
3695 FALSE, FALSE, NULL, NULL);
3696 add_column(view, 2,
3697 propid == OPID_PLAYER_GOV ? _("Icon") : _("Flag"),
3698 GDK_TYPE_PIXBUF,
3699 FALSE, FALSE, NULL, NULL);
3700 add_column(view, 3, _("Name"), G_TYPE_STRING,
3701 FALSE, FALSE, NULL, NULL);
3702 break;
3703
3704 case OPID_PLAYER_INVENTIONS:
3705 /* TRANS: As in "this invention is known". */
3706 add_column(view, 0, _("Known"), G_TYPE_BOOLEAN, TRUE, FALSE,
3707 G_CALLBACK(extviewer_view_cell_toggled), ev);
3708 add_column(view, 1, _("ID"), G_TYPE_INT,
3709 FALSE, FALSE, NULL, NULL);
3710 add_column(view, 2, _("Name"), G_TYPE_STRING,
3711 FALSE, FALSE, NULL, NULL);
3712 break;
3713
3714 case OPID_GAME_SCENARIO_AUTHORS:
3715 case OPID_GAME_SCENARIO_DESC:
3716 g_signal_connect(textbuf, "changed",
3717 G_CALLBACK(extviewer_textbuf_changed), ev);
3718 break;
3719
3720 default:
3721 log_error("Unhandled request to configure view widget "
3722 "for property %d (%s) in extviewer_new().",
3723 propid, objprop_get_name(op));
3724 break;
3725 }
3726
3727 gtk_widget_show_all(ev->panel_widget);
3728 gtk_widget_show_all(ev->view_widget);
3729
3730 return ev;
3731 }
3732
3733 /****************************************************************************
3734 Returns the object property that is displayed by this extviewer.
3735 ****************************************************************************/
extviewer_get_objprop(struct extviewer * ev)3736 static struct objprop *extviewer_get_objprop(struct extviewer *ev)
3737 {
3738 if (!ev) {
3739 return NULL;
3740 }
3741 return ev->objprop;
3742 }
3743
3744 /****************************************************************************
3745 Returns the "panel widget" for this extviewer, i.e. the widget the
3746 is to be placed into the properties panel.
3747 ****************************************************************************/
extviewer_get_panel_widget(struct extviewer * ev)3748 static GtkWidget *extviewer_get_panel_widget(struct extviewer *ev)
3749 {
3750 if (!ev) {
3751 return NULL;
3752 }
3753 return ev->panel_widget;
3754 }
3755
3756 /****************************************************************************
3757 Returns the "view widget" for this extviewer, i.e. the widget the
3758 is to be used for viewing/editing a complex property.
3759 ****************************************************************************/
extviewer_get_view_widget(struct extviewer * ev)3760 static GtkWidget *extviewer_get_view_widget(struct extviewer *ev)
3761 {
3762 if (!ev) {
3763 return NULL;
3764 }
3765 return ev->view_widget;
3766 }
3767
3768 /****************************************************************************
3769 Set the widgets in the extended property viewer to display the given value.
3770 ****************************************************************************/
extviewer_refresh_widgets(struct extviewer * ev,struct propval * pv)3771 static void extviewer_refresh_widgets(struct extviewer *ev,
3772 struct propval *pv)
3773 {
3774 struct objprop *op;
3775 enum object_property_ids propid;
3776 int id, turn_built;
3777 bool present, all;
3778 const char *name;
3779 GdkPixbuf *pixbuf;
3780 GtkListStore *store;
3781 GtkTextBuffer *textbuf;
3782 GtkTreeIter iter;
3783 gchar *buf;
3784
3785 if (!ev) {
3786 return;
3787 }
3788
3789 op = extviewer_get_objprop(ev);
3790 propid = objprop_get_id(op);
3791
3792 if (propval_equal(pv, ev->pv_cached)) {
3793 return;
3794 }
3795 propval_free(ev->pv_cached);
3796 ev->pv_cached = propval_copy(pv);
3797 store = ev->store;
3798 textbuf = ev->textbuf;
3799
3800
3801 /* NB: Remember to have -1 as the last argument to
3802 * gtk_list_store_set() and to use the correct column
3803 * number when inserting data. :) */
3804 switch (propid) {
3805
3806 case OPID_TILE_SPECIALS:
3807 gtk_list_store_clear(store);
3808 extra_type_by_cause_iterate(EC_SPECIAL, spe) {
3809 id = spe->data.special_idx;
3810 name = extra_name_translation(spe);
3811 present = BV_ISSET(pv->data.v_bv_special, id);
3812 gtk_list_store_append(store, &iter);
3813 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3814 } extra_type_by_cause_iterate_end;
3815 buf = propval_as_string(pv);
3816 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3817 g_free(buf);
3818 break;
3819
3820 case OPID_TILE_ROADS:
3821 gtk_list_store_clear(store);
3822 extra_type_by_cause_iterate(EC_ROAD, pextra) {
3823 struct road_type *proad = extra_road_get(pextra);
3824
3825 id = road_number(proad);
3826 name = extra_name_translation(pextra);
3827 present = BV_ISSET(pv->data.v_bv_roads, id);
3828 gtk_list_store_append(store, &iter);
3829 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3830 } extra_type_by_cause_iterate_end;
3831 buf = propval_as_string(pv);
3832 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3833 g_free(buf);
3834 break;
3835
3836 case OPID_TILE_BASES:
3837 gtk_list_store_clear(store);
3838 extra_type_by_cause_iterate(EC_BASE, pextra) {
3839 struct base_type *pbase = extra_base_get(pextra);
3840
3841 id = base_number(pbase);
3842 name = extra_name_translation(pextra);
3843 present = BV_ISSET(pv->data.v_bv_bases, id);
3844 gtk_list_store_append(store, &iter);
3845 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3846 } extra_type_by_cause_iterate_end;
3847 buf = propval_as_string(pv);
3848 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3849 g_free(buf);
3850 break;
3851
3852 case OPID_TILE_VISION:
3853 gtk_list_store_clear(store);
3854 player_slots_iterate(pslot) {
3855 id = player_slot_index(pslot);
3856 if (player_slot_is_used(pslot)) {
3857 struct player *pplayer = player_slot_get_player(pslot);
3858 name = player_name(pplayer);
3859 pixbuf = get_flag(pplayer->nation);
3860 } else {
3861 name = "";
3862 pixbuf = NULL;
3863 }
3864 gtk_list_store_append(store, &iter);
3865 gtk_list_store_set(store, &iter, 0, id, 2, name, -1);
3866 if (pixbuf) {
3867 gtk_list_store_set(store, &iter, 1, pixbuf, -1);
3868 g_object_unref(pixbuf);
3869 pixbuf = NULL;
3870 }
3871 present = BV_ISSET(pv->data.v_tile_vision->tile_known, id);
3872 gtk_list_store_set(store, &iter, 3, present, -1);
3873 vision_layer_iterate(v) {
3874 present = BV_ISSET(pv->data.v_tile_vision->tile_seen[v], id);
3875 gtk_list_store_set(store, &iter, 4 + v, present, -1);
3876 } vision_layer_iterate_end;
3877 } player_slots_iterate_end;
3878 break;
3879
3880 case OPID_STARTPOS_NATIONS:
3881 gtk_list_store_clear(store);
3882 gtk_list_store_append(store, &iter);
3883 all = (0 == nation_hash_size(pv->data.v_nation_hash));
3884 gtk_list_store_set(store, &iter, 0, all, 1, -1, 3,
3885 _("All nations"), -1);
3886 nations_iterate(pnation) {
3887 if (client_nation_is_in_current_set(pnation)
3888 && is_nation_playable(pnation)) {
3889 present = (!all && nation_hash_lookup(pv->data.v_nation_hash,
3890 pnation, NULL));
3891 id = nation_number(pnation);
3892 pixbuf = get_flag(pnation);
3893 name = nation_adjective_translation(pnation);
3894 gtk_list_store_append(store, &iter);
3895 gtk_list_store_set(store, &iter, 0, present, 1, id,
3896 2, pixbuf, 3, name, -1);
3897 if (pixbuf) {
3898 g_object_unref(pixbuf);
3899 }
3900 }
3901 } nations_iterate_end;
3902 buf = propval_as_string(pv);
3903 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3904 g_free(buf);
3905 break;
3906
3907 case OPID_CITY_BUILDINGS:
3908 gtk_list_store_clear(store);
3909 improvement_iterate(pimprove) {
3910 if (is_special_improvement(pimprove)) {
3911 continue;
3912 }
3913 id = improvement_index(pimprove);
3914 name = improvement_name_translation(pimprove);
3915 turn_built = pv->data.v_built[id].turn;
3916 present = turn_built >= 0;
3917 buf = built_status_to_string(&pv->data.v_built[id]);
3918 gtk_list_store_append(store, &iter);
3919 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name,
3920 3, buf, -1);
3921 g_free(buf);
3922 } improvement_iterate_end;
3923 buf = propval_as_string(pv);
3924 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3925 g_free(buf);
3926 break;
3927
3928 case OPID_PLAYER_NATION:
3929 {
3930 enum barbarian_type barbarian_type =
3931 nation_barbarian_type(pv->data.v_nation);
3932
3933 gtk_list_store_clear(store);
3934 nations_iterate(pnation) {
3935 if (client_nation_is_in_current_set(pnation)
3936 && nation_barbarian_type(pnation) == barbarian_type
3937 && (barbarian_type != NOT_A_BARBARIAN
3938 || is_nation_playable(pnation))) {
3939 present = (pnation == pv->data.v_nation);
3940 id = nation_index(pnation);
3941 pixbuf = get_flag(pnation);
3942 name = nation_adjective_translation(pnation);
3943 gtk_list_store_append(store, &iter);
3944 gtk_list_store_set(store, &iter, 0, present, 1, id,
3945 2, pixbuf, 3, name, -1);
3946 if (pixbuf) {
3947 g_object_unref(pixbuf);
3948 }
3949 }
3950 } nations_iterate_end;
3951 gtk_label_set_text(GTK_LABEL(ev->panel_label),
3952 nation_adjective_translation(pv->data.v_nation));
3953 pixbuf = get_flag(pv->data.v_nation);
3954 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
3955 if (pixbuf) {
3956 g_object_unref(pixbuf);
3957 }
3958 }
3959 break;
3960
3961 case OPID_PLAYER_GOV:
3962 {
3963 gtk_list_store_clear(store);
3964 governments_iterate(pgov) {
3965 present = (pgov == pv->data.v_gov);
3966 id = government_index(pgov);
3967 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pgov));
3968 name = government_name_translation(pgov);
3969 gtk_list_store_append(store, &iter);
3970 gtk_list_store_set(store, &iter, 0, present, 1, id,
3971 2, pixbuf, 3, name, -1);
3972 if (pixbuf) {
3973 g_object_unref(pixbuf);
3974 }
3975 } governments_iterate_end;
3976 gtk_label_set_text(GTK_LABEL(ev->panel_label),
3977 government_name_translation(pv->data.v_gov));
3978 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
3979 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
3980 if (pixbuf) {
3981 g_object_unref(pixbuf);
3982 }
3983 }
3984 break;
3985
3986 case OPID_PLAYER_INVENTIONS:
3987 gtk_list_store_clear(store);
3988 advance_iterate(A_FIRST, padvance) {
3989 id = advance_index(padvance);
3990 present = BV_ISSET(pv->data.v_bv_inventions, id);
3991 name = advance_name_translation(padvance);
3992 gtk_list_store_append(store, &iter);
3993 gtk_list_store_set(store, &iter, 0, present, 1, id, 2, name, -1);
3994 } advance_iterate_end;
3995 buf = propval_as_string(pv);
3996 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
3997 g_free(buf);
3998 break;
3999
4000 case OPID_GAME_SCENARIO_AUTHORS:
4001 case OPID_GAME_SCENARIO_DESC:
4002 disable_gobject_callback(G_OBJECT(ev->textbuf),
4003 G_CALLBACK(extviewer_textbuf_changed));
4004 {
4005 GtkTextIter start, end;
4006 char *oldtext;
4007
4008 /* Don't re-set content if unchanged, to avoid moving cursor */
4009 gtk_text_buffer_get_bounds(textbuf, &start, &end);
4010 oldtext = gtk_text_buffer_get_text(textbuf, &start, &end, TRUE);
4011 if (strcmp(oldtext, pv->data.v_const_string) != 0) {
4012 gtk_text_buffer_set_text(textbuf, pv->data.v_const_string, -1);
4013 }
4014 }
4015 enable_gobject_callback(G_OBJECT(ev->textbuf),
4016 G_CALLBACK(extviewer_textbuf_changed));
4017 gtk_widget_set_sensitive(ev->view_widget, TRUE);
4018 buf = propval_as_string(pv);
4019 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4020 g_free(buf);
4021 break;
4022
4023 default:
4024 log_error("Unhandled request to refresh widgets "
4025 "extviewer_refresh_widgets() for objprop id=%d "
4026 "name \"%s\".", propid, objprop_get_name(op));
4027 break;
4028 }
4029 }
4030
4031 /****************************************************************************
4032 Clear the display widgets.
4033 ****************************************************************************/
extviewer_clear_widgets(struct extviewer * ev)4034 static void extviewer_clear_widgets(struct extviewer *ev)
4035 {
4036 struct objprop *op;
4037 enum object_property_ids propid;
4038
4039 if (!ev) {
4040 return;
4041 }
4042
4043 op = extviewer_get_objprop(ev);
4044 propid = objprop_get_id(op);
4045
4046 propval_free(ev->pv_cached);
4047 ev->pv_cached = NULL;
4048
4049 if (ev->panel_label != NULL) {
4050 gtk_label_set_text(GTK_LABEL(ev->panel_label), NULL);
4051 }
4052
4053 switch (propid) {
4054 case OPID_TILE_SPECIALS:
4055 case OPID_TILE_ROADS:
4056 case OPID_TILE_BASES:
4057 case OPID_TILE_VISION:
4058 case OPID_STARTPOS_NATIONS:
4059 case OPID_CITY_BUILDINGS:
4060 case OPID_PLAYER_INVENTIONS:
4061 gtk_list_store_clear(ev->store);
4062 break;
4063 case OPID_PLAYER_NATION:
4064 case OPID_PLAYER_GOV:
4065 gtk_list_store_clear(ev->store);
4066 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), NULL);
4067 break;
4068 case OPID_GAME_SCENARIO_AUTHORS:
4069 case OPID_GAME_SCENARIO_DESC:
4070 disable_gobject_callback(G_OBJECT(ev->textbuf),
4071 G_CALLBACK(extviewer_textbuf_changed));
4072 gtk_text_buffer_set_text(ev->textbuf, "", -1);
4073 enable_gobject_callback(G_OBJECT(ev->textbuf),
4074 G_CALLBACK(extviewer_textbuf_changed));
4075 gtk_widget_set_sensitive(ev->view_widget, FALSE);
4076 break;
4077 default:
4078 log_error("Unhandled request to clear widgets "
4079 "in extviewer_clear_widgets() for objprop id=%d "
4080 "name \"%s\".", propid, objprop_get_name(op));
4081 break;
4082 }
4083 }
4084
4085 /****************************************************************************
4086 Handle the extviewer's panel button click. This should set the property
4087 page to display the view widget for this complex property.
4088 ****************************************************************************/
extviewer_panel_button_clicked(GtkButton * button,gpointer userdata)4089 static void extviewer_panel_button_clicked(GtkButton *button,
4090 gpointer userdata)
4091 {
4092 struct extviewer *ev;
4093 struct property_page *pp;
4094 struct objprop *op;
4095
4096 ev = userdata;
4097 if (!ev) {
4098 return;
4099 }
4100
4101 op = extviewer_get_objprop(ev);
4102 pp = objprop_get_property_page(op);
4103 property_page_show_extviewer(pp, ev);
4104 }
4105
4106 /****************************************************************************
4107 Handle toggling of a boolean cell value in the extviewer's tree view.
4108 ****************************************************************************/
extviewer_view_cell_toggled(GtkCellRendererToggle * cell,gchar * path,gpointer userdata)4109 static void extviewer_view_cell_toggled(GtkCellRendererToggle *cell,
4110 gchar *path,
4111 gpointer userdata)
4112 {
4113 struct extviewer *ev;
4114 struct objprop *op;
4115 struct property_page *pp;
4116 enum object_property_ids propid;
4117 GtkTreeModel *model;
4118 GtkTreeIter iter;
4119 int id, old_id, turn_built;
4120 struct propval *pv;
4121 bool active, present;
4122 gchar *buf;
4123 GdkPixbuf *pixbuf = NULL;
4124
4125 ev = userdata;
4126 if (!ev) {
4127 return;
4128 }
4129
4130 pv = ev->pv_cached;
4131 if (!pv) {
4132 return;
4133 }
4134
4135 op = extviewer_get_objprop(ev);
4136 propid = objprop_get_id(op);
4137 active = gtk_cell_renderer_toggle_get_active(cell);
4138 pp = objprop_get_property_page(op);
4139
4140 model = GTK_TREE_MODEL(ev->store);
4141 if (!gtk_tree_model_get_iter_from_string(model, &iter, path)) {
4142 return;
4143 }
4144 present = !active;
4145
4146
4147 switch (propid) {
4148
4149 case OPID_TILE_SPECIALS:
4150 gtk_tree_model_get(model, &iter, 1, &id, -1);
4151 if (id < 0 || id >= extra_type_list_size(extra_type_list_by_cause(EC_SPECIAL))) {
4152 return;
4153 }
4154 if (present) {
4155 BV_SET(pv->data.v_bv_special, id);
4156 } else {
4157 BV_CLR(pv->data.v_bv_special, id);
4158 }
4159 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4160 buf = propval_as_string(pv);
4161 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4162 g_free(buf);
4163 break;
4164
4165 case OPID_TILE_ROADS:
4166 gtk_tree_model_get(model, &iter, 1, &id, -1);
4167 if (!(0 <= id && id < road_count())) {
4168 return;
4169 }
4170 if (present) {
4171 BV_SET(pv->data.v_bv_roads, id);
4172 } else {
4173 BV_CLR(pv->data.v_bv_roads, id);
4174 }
4175 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4176 buf = propval_as_string(pv);
4177 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4178 g_free(buf);
4179 break;
4180
4181 case OPID_TILE_BASES:
4182 gtk_tree_model_get(model, &iter, 1, &id, -1);
4183 if (!(0 <= id && id < base_count())) {
4184 return;
4185 }
4186 if (present) {
4187 BV_SET(pv->data.v_bv_bases, id);
4188 } else {
4189 BV_CLR(pv->data.v_bv_bases, id);
4190 }
4191 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4192 buf = propval_as_string(pv);
4193 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4194 g_free(buf);
4195 break;
4196
4197 case OPID_STARTPOS_NATIONS:
4198 gtk_tree_model_get(model, &iter, 1, &id, -1);
4199 if (-1 > id && id >= nation_count()) {
4200 return;
4201 }
4202
4203 if (-1 == id) {
4204 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4205 gtk_tree_model_get_iter_first(model, &iter);
4206 if (present) {
4207 while (gtk_tree_model_iter_next(model, &iter)) {
4208 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4209 }
4210 nation_hash_clear(pv->data.v_nation_hash);
4211 } else {
4212 const struct nation_type *pnation;
4213 int id2;
4214
4215 gtk_tree_model_iter_next(model, &iter);
4216 gtk_tree_model_get(model, &iter, 0, &id2, -1);
4217 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4218 pnation = nation_by_number(id2);
4219 nation_hash_insert(pv->data.v_nation_hash, pnation, NULL);
4220 }
4221 } else {
4222 const struct nation_type *pnation;
4223 bool all;
4224
4225 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4226 pnation = nation_by_number(id);
4227 if (present) {
4228 nation_hash_insert(pv->data.v_nation_hash, pnation, NULL);
4229 } else {
4230 nation_hash_remove(pv->data.v_nation_hash, pnation);
4231 }
4232 gtk_tree_model_get_iter_first(model, &iter);
4233 all = (0 == nation_hash_size(pv->data.v_nation_hash));
4234 gtk_list_store_set(ev->store, &iter, 0, all, -1);
4235 }
4236 buf = propval_as_string(pv);
4237 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4238 g_free(buf);
4239 break;
4240
4241 case OPID_CITY_BUILDINGS:
4242 gtk_tree_model_get(model, &iter, 1, &id, -1);
4243 if (!(0 <= id && id < B_LAST)) {
4244 return;
4245 }
4246 turn_built = present ? game.info.turn : I_NEVER;
4247 pv->data.v_built[id].turn = turn_built;
4248 buf = built_status_to_string(&pv->data.v_built[id]);
4249 gtk_list_store_set(ev->store, &iter, 0, present, 3, buf, -1);
4250 g_free(buf);
4251 buf = propval_as_string(pv);
4252 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4253 g_free(buf);
4254 break;
4255
4256 case OPID_PLAYER_NATION:
4257 gtk_tree_model_get(model, &iter, 1, &id, -1);
4258 if (!(0 <= id && id < nation_count()) || !present) {
4259 return;
4260 }
4261 old_id = nation_index(pv->data.v_nation);
4262 pv->data.v_nation = nation_by_number(id);
4263 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4264 gtk_tree_model_iter_nth_child(model, &iter, NULL, old_id);
4265 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4266 gtk_label_set_text(GTK_LABEL(ev->panel_label),
4267 nation_adjective_translation(pv->data.v_nation));
4268 pixbuf = get_flag(pv->data.v_nation);
4269 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
4270 if (pixbuf) {
4271 g_object_unref(pixbuf);
4272 }
4273 break;
4274
4275 case OPID_PLAYER_GOV:
4276 gtk_tree_model_get(model, &iter, 1, &id, -1);
4277 if (!(0 <= id && id < government_count()) || !present) {
4278 return;
4279 }
4280 old_id = government_index(pv->data.v_gov);
4281 pv->data.v_gov = government_by_number(id);
4282 gtk_list_store_set(ev->store, &iter, 0, TRUE, -1);
4283 gtk_tree_model_iter_nth_child(model, &iter, NULL, old_id);
4284 gtk_list_store_set(ev->store, &iter, 0, FALSE, -1);
4285 gtk_label_set_text(GTK_LABEL(ev->panel_label),
4286 government_name_translation(pv->data.v_gov));
4287 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
4288 gtk_image_set_from_pixbuf(GTK_IMAGE(ev->panel_image), pixbuf);
4289 if (pixbuf) {
4290 g_object_unref(pixbuf);
4291 }
4292 break;
4293
4294 case OPID_PLAYER_INVENTIONS:
4295 gtk_tree_model_get(model, &iter, 1, &id, -1);
4296 if (!(A_FIRST <= id && id < advance_count())) {
4297 return;
4298 }
4299 if (present) {
4300 BV_SET(pv->data.v_bv_inventions, id);
4301 } else {
4302 BV_CLR(pv->data.v_bv_inventions, id);
4303 }
4304 gtk_list_store_set(ev->store, &iter, 0, present, -1);
4305 buf = propval_as_string(pv);
4306 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4307 g_free(buf);
4308 break;
4309
4310 default:
4311 log_error("Unhandled widget toggled signal in "
4312 "extviewer_view_cell_toggled() for objprop id=%d "
4313 "name \"%s\".", propid, objprop_get_name(op));
4314 return;
4315 break;
4316 }
4317
4318 property_page_change_value(pp, op, pv);
4319 }
4320
4321 /****************************************************************************
4322 Handle a change in the extviewer's text buffer.
4323 ****************************************************************************/
extviewer_textbuf_changed(GtkTextBuffer * textbuf,gpointer userdata)4324 static void extviewer_textbuf_changed(GtkTextBuffer *textbuf,
4325 gpointer userdata)
4326 {
4327 struct extviewer *ev;
4328 struct objprop *op;
4329 struct property_page *pp;
4330 enum object_property_ids propid;
4331 struct propval value = {{0,}, VALTYPE_STRING, FALSE}, *pv;
4332 GtkTextIter start, end;
4333 char *text;
4334 gchar *buf;
4335
4336 ev = userdata;
4337 if (!ev) {
4338 return;
4339 }
4340
4341 op = extviewer_get_objprop(ev);
4342 propid = objprop_get_id(op);
4343 pp = objprop_get_property_page(op);
4344
4345 gtk_text_buffer_get_start_iter(textbuf, &start);
4346 gtk_text_buffer_get_end_iter(textbuf, &end);
4347 text = gtk_text_buffer_get_text(textbuf, &start, &end, FALSE);
4348 value.data.v_const_string = text;
4349 pv = &value;
4350
4351 switch (propid) {
4352 case OPID_GAME_SCENARIO_AUTHORS:
4353 case OPID_GAME_SCENARIO_DESC:
4354 buf = propval_as_string(pv);
4355 gtk_label_set_text(GTK_LABEL(ev->panel_label), buf);
4356 g_free(buf);
4357 break;
4358 default:
4359 log_error("Unhandled widget modified signal in "
4360 "extviewer_textbuf_changed() for objprop id=%d "
4361 "name \"%s\".", propid, objprop_get_name(op));
4362 return;
4363 break;
4364 }
4365
4366 property_page_change_value(pp, op, pv);
4367 g_free(text);
4368 }
4369
4370 /****************************************************************************
4371 Install all object properties that this page type can view/edit.
4372 ****************************************************************************/
property_page_setup_objprops(struct property_page * pp)4373 static void property_page_setup_objprops(struct property_page *pp)
4374 {
4375 #define ADDPROP(ARG_id, ARG_name, ARG_tooltip, ARG_flags, ARG_valtype) do { \
4376 struct objprop *MY_op = objprop_new(ARG_id, ARG_name, ARG_tooltip, \
4377 ARG_flags, ARG_valtype, pp); \
4378 objprop_hash_insert(pp->objprop_table, MY_op->id, MY_op); \
4379 } while (FALSE)
4380
4381 switch (property_page_get_objtype(pp)) {
4382 case OBJTYPE_TILE:
4383 ADDPROP(OPID_TILE_IMAGE, _("Image"), NULL,
4384 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4385 ADDPROP(OPID_TILE_TERRAIN, _("Terrain"), NULL,
4386 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4387 ADDPROP(OPID_TILE_RESOURCE, _("Resource"), NULL,
4388 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4389 ADDPROP(OPID_TILE_INDEX, _("Index"), NULL,
4390 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4391 ADDPROP(OPID_TILE_X, Q_("?coordinate:X"), NULL,
4392 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4393 ADDPROP(OPID_TILE_Y, Q_("?coordinate:Y"), NULL,
4394 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4395 /* TRANS: The coordinate X in native coordinates.
4396 * The freeciv coordinate system is described in doc/HACKING. */
4397 ADDPROP(OPID_TILE_NAT_X, _("NAT X"), NULL,
4398 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4399 /* TRANS: The coordinate Y in native coordinates.
4400 * The freeciv coordinate system is described in doc/HACKING. */
4401 ADDPROP(OPID_TILE_NAT_Y, _("NAT Y"), NULL,
4402 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4403 ADDPROP(OPID_TILE_CONTINENT, _("Continent"), NULL,
4404 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4405 ADDPROP(OPID_TILE_XY, Q_("?coordinates:X,Y"), NULL,
4406 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4407 ADDPROP(OPID_TILE_SPECIALS, _("Specials"), NULL,
4408 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4409 VALTYPE_BV_SPECIAL);
4410 ADDPROP(OPID_TILE_ROADS, _("Roads"), NULL,
4411 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4412 VALTYPE_BV_ROADS);
4413 ADDPROP(OPID_TILE_BASES, _("Bases"), NULL,
4414 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4415 VALTYPE_BV_BASES);
4416 #ifdef FREECIV_DEBUG
4417 ADDPROP(OPID_TILE_ADDRESS, _("Address"), NULL,
4418 OPF_HAS_WIDGET, VALTYPE_STRING);
4419 #endif /* FREECIV_DEBUG */
4420 #if 0
4421 /* Disabled entirely for now as server is not sending other
4422 * players' vision information anyway. */
4423 ADDPROP(OPID_TILE_VISION, _("Vision"), NULL,
4424 OPF_HAS_WIDGET, VALTYPE_TILE_VISION_DATA);
4425 #endif
4426 /* TRANS: Tile property "Label" label in editor */
4427 ADDPROP(OPID_TILE_LABEL, Q_("?property:Label"), NULL,
4428 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_STRING);
4429 return;
4430
4431 case OBJTYPE_STARTPOS:
4432 ADDPROP(OPID_STARTPOS_IMAGE, _("Image"), NULL,
4433 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4434 ADDPROP(OPID_STARTPOS_XY, Q_("?coordinates:X,Y"), NULL,
4435 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4436 ADDPROP(OPID_STARTPOS_EXCLUDE, _("Exclude Nations"), NULL,
4437 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4438 ADDPROP(OPID_STARTPOS_NATIONS, _("Nations"), NULL,
4439 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4440 VALTYPE_NATION_HASH);
4441 return;
4442
4443 case OBJTYPE_UNIT:
4444 ADDPROP(OPID_UNIT_IMAGE, _("Image"), NULL,
4445 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4446 #ifdef FREECIV_DEBUG
4447 ADDPROP(OPID_UNIT_ADDRESS, _("Address"), NULL,
4448 OPF_HAS_WIDGET, VALTYPE_STRING);
4449 #endif /* FREECIV_DEBUG */
4450 ADDPROP(OPID_UNIT_TYPE, _("Type"), NULL,
4451 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4452 ADDPROP(OPID_UNIT_ID, _("ID"), NULL,
4453 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4454 ADDPROP(OPID_UNIT_XY, Q_("?coordinates:X,Y"), NULL,
4455 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4456 ADDPROP(OPID_UNIT_MOVES_LEFT, _("Moves Left"), NULL,
4457 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4458 ADDPROP(OPID_UNIT_FUEL, _("Fuel"), NULL,
4459 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4460 ADDPROP(OPID_UNIT_MOVED, _("Moved"), NULL,
4461 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4462 ADDPROP(OPID_UNIT_DONE_MOVING, _("Done Moving"), NULL,
4463 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4464 /* TRANS: HP = Hit Points of a unit. */
4465 ADDPROP(OPID_UNIT_HP, _("HP"), NULL,
4466 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4467 ADDPROP(OPID_UNIT_VETERAN, _("Veteran"), NULL,
4468 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4469 return;
4470
4471 case OBJTYPE_CITY:
4472 ADDPROP(OPID_CITY_IMAGE, _("Image"), NULL,
4473 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_PIXBUF);
4474 ADDPROP(OPID_CITY_NAME, _("Name"), NULL,
4475 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_STRING);
4476 #ifdef FREECIV_DEBUG
4477 ADDPROP(OPID_CITY_ADDRESS, _("Address"), NULL,
4478 OPF_HAS_WIDGET, VALTYPE_STRING);
4479 #endif /* FREECIV_DEBUG */
4480 ADDPROP(OPID_CITY_ID, _("ID"), NULL,
4481 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_INT);
4482 ADDPROP(OPID_CITY_XY, Q_("?coordinates:X,Y"), NULL,
4483 OPF_IN_LISTVIEW | OPF_HAS_WIDGET, VALTYPE_STRING);
4484 ADDPROP(OPID_CITY_SIZE, _("Size"), NULL,
4485 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4486 ADDPROP(OPID_CITY_HISTORY, _("History"), NULL,
4487 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4488 ADDPROP(OPID_CITY_BUILDINGS, _("Buildings"), NULL,
4489 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4490 VALTYPE_BUILT_ARRAY);
4491 ADDPROP(OPID_CITY_FOOD_STOCK, _("Food Stock"), NULL,
4492 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4493 ADDPROP(OPID_CITY_SHIELD_STOCK, _("Shield Stock"), NULL,
4494 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4495 return;
4496
4497 case OBJTYPE_PLAYER:
4498 ADDPROP(OPID_PLAYER_NAME, _("Name"), NULL,
4499 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4500 VALTYPE_STRING);
4501 #ifdef FREECIV_DEBUG
4502 ADDPROP(OPID_PLAYER_ADDRESS, _("Address"), NULL,
4503 OPF_HAS_WIDGET, VALTYPE_STRING);
4504 #endif /* FREECIV_DEBUG */
4505 ADDPROP(OPID_PLAYER_NATION, _("Nation"), NULL,
4506 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4507 VALTYPE_NATION);
4508 ADDPROP(OPID_PLAYER_GOV, _("Government"), NULL,
4509 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4510 VALTYPE_GOV);
4511 ADDPROP(OPID_PLAYER_AGE, _("Age"), NULL,
4512 OPF_HAS_WIDGET, VALTYPE_INT);
4513 ADDPROP(OPID_PLAYER_INVENTIONS, _("Inventions"), NULL,
4514 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4515 VALTYPE_INVENTIONS_ARRAY);
4516 ADDPROP(OPID_PLAYER_SCIENCE, _("Science"), NULL,
4517 OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_INT);
4518 ADDPROP(OPID_PLAYER_GOLD, _("Gold"), NULL,
4519 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4520 VALTYPE_INT);
4521 return;
4522
4523 case OBJTYPE_GAME:
4524 ADDPROP(OPID_GAME_YEAR, _("Year"), NULL,
4525 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4526 VALTYPE_INT);
4527 ADDPROP(OPID_GAME_SCENARIO, _("Scenario"), NULL,
4528 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4529 VALTYPE_BOOL);
4530 ADDPROP(OPID_GAME_SCENARIO_NAME, _("Scenario Name"), NULL,
4531 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4532 VALTYPE_STRING);
4533 ADDPROP(OPID_GAME_SCENARIO_AUTHORS, _("Scenario Authors"), NULL,
4534 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4535 VALTYPE_STRING);
4536 ADDPROP(OPID_GAME_SCENARIO_DESC, _("Scenario Description"), NULL,
4537 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE,
4538 VALTYPE_STRING);
4539 ADDPROP(OPID_GAME_SCENARIO_RANDSTATE,
4540 _("Save Random Number State"), NULL,
4541 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4542 ADDPROP(OPID_GAME_SCENARIO_PLAYERS, _("Save Players"), NULL,
4543 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4544 ADDPROP(OPID_GAME_STARTPOS_NATIONS,
4545 _("Nation Start Positions"), NULL,
4546 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4547 ADDPROP(OPID_GAME_PREVENT_CITIES,
4548 _("Prevent New Cities"), NULL,
4549 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4550 ADDPROP(OPID_GAME_LAKE_FLOODING,
4551 _("Saltwater Flooding Lakes"), NULL,
4552 OPF_IN_LISTVIEW | OPF_HAS_WIDGET | OPF_EDITABLE, VALTYPE_BOOL);
4553 return;
4554
4555 case NUM_OBJTYPES:
4556 break;
4557 }
4558
4559 log_error("%s(): Unhandled page object type %s (nb %d).", __FUNCTION__,
4560 objtype_get_name(property_page_get_objtype(pp)),
4561 property_page_get_objtype(pp));
4562 #undef ADDPROP
4563 }
4564
4565 /****************************************************************************
4566 Callback for when a property page's listview's selection changes.
4567 ****************************************************************************/
property_page_selection_changed(GtkTreeSelection * sel,gpointer userdata)4568 static void property_page_selection_changed(GtkTreeSelection *sel,
4569 gpointer userdata)
4570 {
4571 struct property_page *pp;
4572 struct objbind *ob = NULL;
4573
4574 pp = userdata;
4575 if (!pp) {
4576 return;
4577 }
4578
4579 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
4580 property_page_set_focused_objbind(pp, NULL);
4581 }
4582
4583 ob = property_page_get_focused_objbind(pp);
4584 property_page_objprop_iterate(pp, op) {
4585 objprop_refresh_widget(op, ob);
4586 } property_page_objprop_iterate_end;
4587 }
4588
4589 /****************************************************************************
4590 Monitor which rows are to be selected, so we know which objbind to display
4591 in the properties panel.
4592 ****************************************************************************/
property_page_selection_func(GtkTreeSelection * sel,GtkTreeModel * model,GtkTreePath * sel_path,gboolean currently_selected,gpointer data)4593 static gboolean property_page_selection_func(GtkTreeSelection *sel,
4594 GtkTreeModel *model,
4595 GtkTreePath *sel_path,
4596 gboolean currently_selected,
4597 gpointer data)
4598 {
4599 struct property_page *pp;
4600 struct objbind *ob = NULL, *old_ob;
4601 GtkTreeIter iter;
4602
4603 pp = data;
4604 if (!pp || !sel_path) {
4605 return TRUE;
4606 }
4607
4608 if (!gtk_tree_model_get_iter(model, &iter, sel_path)) {
4609 return TRUE;
4610 }
4611
4612 old_ob = property_page_get_focused_objbind(pp);
4613 gtk_tree_model_get(model, &iter, 0, &ob, -1);
4614 if (currently_selected) {
4615 if (ob == old_ob) {
4616 GList *rows, *p;
4617 GtkTreePath *path;
4618 struct objbind *new_ob = NULL;
4619
4620 rows = gtk_tree_selection_get_selected_rows(sel, NULL);
4621 for (p = rows; p != NULL; p = p->next) {
4622 path = p->data;
4623 if (gtk_tree_model_get_iter(model, &iter, path)) {
4624 struct objbind *test_ob = NULL;
4625 gtk_tree_model_get(model, &iter, 0, &test_ob, -1);
4626 if (test_ob == ob) {
4627 continue;
4628 }
4629 new_ob = test_ob;
4630 break;
4631 }
4632 }
4633 g_list_foreach(rows, (GFunc) gtk_tree_path_free, NULL);
4634 g_list_free(rows);
4635
4636 property_page_set_focused_objbind(pp, new_ob);
4637 }
4638 } else {
4639 property_page_set_focused_objbind(pp, ob);
4640 }
4641
4642 return TRUE;
4643 }
4644
4645 /****************************************************************************
4646 Callback to handle text changing in the quick find entry widget.
4647 ****************************************************************************/
property_page_quick_find_entry_changed(GtkWidget * entry,gpointer userdata)4648 static void property_page_quick_find_entry_changed(GtkWidget *entry,
4649 gpointer userdata)
4650 {
4651 struct property_page *pp;
4652 const gchar *text;
4653 GtkWidget *w;
4654 GtkTreeViewColumn *col;
4655 struct property_filter *pf;
4656 bool matched;
4657
4658 pp = userdata;
4659 text = gtk_entry_get_text(GTK_ENTRY(entry));
4660 pf = property_filter_new(text);
4661
4662 property_page_objprop_iterate(pp, op) {
4663 if (!objprop_has_widget(op)
4664 && !objprop_show_in_listview(op)) {
4665 continue;
4666 }
4667 matched = property_filter_match(pf, op);
4668 w = objprop_get_widget(op);
4669 if (objprop_has_widget(op) && w != NULL) {
4670 if (matched) {
4671 gtk_widget_show(w);
4672 } else {
4673 gtk_widget_hide(w);
4674 }
4675 }
4676 col = objprop_get_treeview_column(op);
4677 if (objprop_show_in_listview(op) && col != NULL) {
4678 gtk_tree_view_column_set_visible(col, matched);
4679 }
4680 } property_page_objprop_iterate_end;
4681
4682 property_filter_free(pf);
4683 }
4684
4685 /****************************************************************************
4686 Create and return a property page of the given object type.
4687 Returns NULL if the page could not be created.
4688 ****************************************************************************/
4689 static struct property_page *
property_page_new(enum editor_object_type objtype,struct property_editor * pe)4690 property_page_new(enum editor_object_type objtype,
4691 struct property_editor *pe)
4692 {
4693 struct property_page *pp;
4694 GtkWidget *vbox, *vbox2, *hbox, *hbox2, *paned, *frame, *w;
4695 GtkWidget *scrollwin, *view, *label, *entry, *notebook;
4696 GtkWidget *button, *hsep, *image;
4697 GtkTreeSelection *sel;
4698 GtkCellRenderer *cell;
4699 GtkTreeViewColumn *col;
4700 GtkSizeGroup *sizegroup;
4701 int num_columns = 0;
4702 GType *gtype_array;
4703 int col_id = 1;
4704 const char *attr_type_str, *name, *tooltip;
4705 gchar *title;
4706
4707 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
4708 return NULL;
4709 }
4710
4711 pp = fc_calloc(1, sizeof(struct property_page));
4712 pp->objtype = objtype;
4713 pp->pe_parent = pe;
4714
4715 sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
4716
4717 pp->objprop_table = objprop_hash_new();
4718 property_page_setup_objprops(pp);
4719
4720 pp->objbind_table = objbind_hash_new();
4721
4722 pp->tag_table = stored_tag_hash_new();
4723
4724 property_page_objprop_iterate(pp, op) {
4725 if (objprop_show_in_listview(op)) {
4726 num_columns++;
4727 }
4728 } property_page_objprop_iterate_end;
4729
4730 /* Column zero in the store holds an objbind
4731 * pointer and is never displayed. */
4732 num_columns++;
4733 gtype_array = fc_malloc(num_columns * sizeof(GType));
4734 gtype_array[0] = G_TYPE_POINTER;
4735
4736 property_page_objprop_iterate(pp, op) {
4737 if (objprop_show_in_listview(op)) {
4738 gtype_array[col_id] = objprop_get_gtype(op);
4739 objprop_set_column_id(op, col_id);
4740 col_id++;
4741 }
4742 } property_page_objprop_iterate_end;
4743
4744 pp->object_store = gtk_list_store_newv(num_columns, gtype_array);
4745 free(gtype_array);
4746
4747 paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
4748 gtk_paned_set_position(GTK_PANED(paned), 256);
4749 pp->widget = paned;
4750
4751 /* Left side object list view. */
4752
4753 vbox = gtk_grid_new();
4754 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
4755 GTK_ORIENTATION_VERTICAL);
4756 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
4757 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
4758 gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
4759
4760 scrollwin = gtk_scrolled_window_new(NULL, NULL);
4761 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
4762 GTK_SHADOW_ETCHED_IN);
4763 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
4764 GTK_POLICY_AUTOMATIC,
4765 GTK_POLICY_AUTOMATIC);
4766 gtk_container_add(GTK_CONTAINER(vbox), scrollwin);
4767
4768 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pp->object_store));
4769 gtk_widget_set_hexpand(view, TRUE);
4770 gtk_widget_set_vexpand(view, TRUE);
4771
4772 property_page_objprop_iterate(pp, op) {
4773 if (!objprop_show_in_listview(op)) {
4774 continue;
4775 }
4776
4777 attr_type_str = objprop_get_attribute_type_string(op);
4778 if (!attr_type_str) {
4779 continue;
4780 }
4781 col_id = objprop_get_column_id(op);
4782 if (col_id < 0) {
4783 continue;
4784 }
4785 name = objprop_get_name(op);
4786 if (!name) {
4787 continue;
4788 }
4789 cell = objprop_create_cell_renderer(op);
4790 if (!cell) {
4791 continue;
4792 }
4793
4794 col = gtk_tree_view_column_new_with_attributes(name, cell,
4795 attr_type_str, col_id,
4796 NULL);
4797
4798 gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
4799 gtk_tree_view_column_set_resizable(col, TRUE);
4800 gtk_tree_view_column_set_reorderable(col, TRUE);
4801 if (objprop_is_sortable(op)) {
4802 gtk_tree_view_column_set_clickable(col, TRUE);
4803 gtk_tree_view_column_set_sort_column_id(col, col_id);
4804 } else {
4805 gtk_tree_view_column_set_clickable(col, FALSE);
4806 }
4807 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
4808 objprop_set_treeview_column(op, col);
4809
4810 } property_page_objprop_iterate_end;
4811
4812 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
4813 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
4814 g_signal_connect(sel, "changed",
4815 G_CALLBACK(property_page_selection_changed), pp);
4816 gtk_tree_selection_set_select_function(sel,
4817 property_page_selection_func, pp, NULL);
4818
4819 gtk_container_add(GTK_CONTAINER(scrollwin), view);
4820 pp->object_view = view;
4821
4822 if (!objtype_is_conserved(objtype)) {
4823 hbox = gtk_grid_new();
4824 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
4825 gtk_container_add(GTK_CONTAINER(vbox), hbox);
4826
4827 button = gtk_button_new();
4828 image = gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_BUTTON);
4829 gtk_button_set_image(GTK_BUTTON(button), image);
4830 gtk_button_set_label(GTK_BUTTON(button), _("Create"));
4831 gtk_size_group_add_widget(sizegroup, button);
4832 gtk_widget_set_tooltip_text(button,
4833 _("Pressing this button will create a new object of the "
4834 "same type as the current property page and add it to "
4835 "the page. The specific type and count of the objects "
4836 "is taken from the editor tool state. So for example, "
4837 "the \"tool value\" of the unit tool and its \"count\" "
4838 "parameter affect unit creation."));
4839 g_signal_connect(button, "clicked",
4840 G_CALLBACK(property_page_create_button_clicked), pp);
4841 gtk_container_add(GTK_CONTAINER(hbox), button);
4842
4843 button = gtk_button_new();
4844 image = gtk_image_new_from_icon_name("list-remove",
4845 GTK_ICON_SIZE_BUTTON);
4846 gtk_button_set_image(GTK_BUTTON(button), image);
4847 gtk_button_set_label(GTK_BUTTON(button), _("Destroy"));
4848 gtk_size_group_add_widget(sizegroup, button);
4849 gtk_widget_set_tooltip_text(button,
4850 _("Pressing this button will send a request to the server "
4851 "to destroy (i.e. erase) the objects selected in the object "
4852 "list."));
4853 g_signal_connect(button, "clicked",
4854 G_CALLBACK(property_page_destroy_button_clicked), pp);
4855 gtk_container_add(GTK_CONTAINER(hbox), button);
4856 }
4857
4858 /* Right side properties panel. */
4859
4860 hbox = gtk_grid_new();
4861 gtk_grid_set_column_spacing(GTK_GRID(hbox), 4);
4862 gtk_paned_pack2(GTK_PANED(paned), hbox, TRUE, TRUE);
4863
4864 vbox = gtk_grid_new();
4865 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox),
4866 GTK_ORIENTATION_VERTICAL);
4867 gtk_grid_set_row_spacing(GTK_GRID(vbox), 4);
4868 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
4869 gtk_container_add(GTK_CONTAINER(hbox), vbox);
4870
4871 /* Extended property viewer to the right of the properties panel.
4872 * This needs to be created before property widgets, since some
4873 * might try to append themselves to this notebook. */
4874
4875 vbox2 = gtk_grid_new();
4876 gtk_widget_set_hexpand(vbox2, TRUE);
4877 gtk_widget_set_vexpand(vbox2, TRUE);
4878 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox2),
4879 GTK_ORIENTATION_VERTICAL);
4880 gtk_grid_set_row_spacing(GTK_GRID(vbox2), 4);
4881 gtk_container_add(GTK_CONTAINER(hbox), vbox2);
4882
4883 notebook = gtk_notebook_new();
4884 gtk_widget_set_vexpand(notebook, TRUE);
4885 gtk_widget_set_size_request(notebook, 256, -1);
4886 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
4887 gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
4888 gtk_container_add(GTK_CONTAINER(vbox2), notebook);
4889 pp->extviewer_notebook = notebook;
4890
4891 hsep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
4892 gtk_container_add(GTK_CONTAINER(vbox2), hsep);
4893
4894 hbox2 = gtk_grid_new();
4895 gtk_container_set_border_width(GTK_CONTAINER(hbox2), 4);
4896 gtk_container_add(GTK_CONTAINER(vbox2), hbox2);
4897
4898 button = gtk_button_new_with_mnemonic(_("_Close"));
4899 gtk_size_group_add_widget(sizegroup, button);
4900 g_signal_connect_swapped(button, "clicked",
4901 G_CALLBACK(gtk_widget_hide_on_delete), pe->widget);
4902 gtk_container_add(GTK_CONTAINER(hbox2), button);
4903
4904 /* Now create the properties panel. */
4905
4906 /* TRANS: %s is a type of object that can be edited, such as "Tile",
4907 * "Unit", "Start Position", etc. */
4908 title = g_strdup_printf(_("%s Properties"),
4909 objtype_get_name(objtype));
4910 frame = gtk_frame_new(title);
4911 g_free(title);
4912 gtk_widget_set_size_request(frame, 256, -1);
4913 gtk_container_add(GTK_CONTAINER(vbox), frame);
4914
4915 scrollwin = gtk_scrolled_window_new(NULL, NULL);
4916 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin),
4917 GTK_SHADOW_NONE);
4918 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
4919 GTK_POLICY_AUTOMATIC,
4920 GTK_POLICY_AUTOMATIC);
4921 gtk_container_add(GTK_CONTAINER(frame), scrollwin);
4922
4923 vbox2 = gtk_grid_new();
4924 gtk_widget_set_vexpand(vbox2, TRUE);
4925 gtk_orientable_set_orientation(GTK_ORIENTABLE(vbox2),
4926 GTK_ORIENTATION_VERTICAL);
4927 gtk_grid_set_row_spacing(GTK_GRID(vbox2), 4);
4928 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 4);
4929 gtk_container_add(GTK_CONTAINER(scrollwin), vbox2);
4930
4931 property_page_objprop_iterate(pp, op) {
4932 if (!objprop_has_widget(op)) {
4933 continue;
4934 }
4935 w = objprop_get_widget(op);
4936 if (!w) {
4937 continue;
4938 }
4939 gtk_container_add(GTK_CONTAINER(vbox2), w);
4940 tooltip = objprop_get_tooltip(op);
4941 if (NULL != tooltip) {
4942 gtk_widget_set_tooltip_text(w, tooltip);
4943 }
4944 } property_page_objprop_iterate_end;
4945
4946 hbox2 = gtk_grid_new();
4947 gtk_widget_set_margin_top(hbox2, 4);
4948 gtk_widget_set_margin_bottom(hbox2, 4);
4949 gtk_grid_set_column_spacing(GTK_GRID(hbox2), 4);
4950 gtk_container_add(GTK_CONTAINER(vbox), hbox2);
4951
4952 label = gtk_label_new(_("Filter:"));
4953 gtk_container_add(GTK_CONTAINER(hbox2), label);
4954
4955 entry = gtk_entry_new();
4956 gtk_widget_set_tooltip_text(entry,
4957 _("Enter a filter string to limit which properties are shown. "
4958 "The filter is one or more text patterns separated by | "
4959 "(\"or\") or & (\"and\"). The symbol & has higher precedence "
4960 "than |. A pattern may also be negated by prefixing it with !."));
4961 g_signal_connect(entry, "changed",
4962 G_CALLBACK(property_page_quick_find_entry_changed), pp);
4963 gtk_container_add(GTK_CONTAINER(hbox2), entry);
4964
4965 hbox2 = gtk_grid_new();
4966 gtk_grid_set_column_spacing(GTK_GRID(hbox2), 4);
4967 gtk_container_add(GTK_CONTAINER(vbox), hbox2);
4968
4969 button = gtk_button_new_with_mnemonic(_("_Refresh"));
4970 gtk_size_group_add_widget(sizegroup, button);
4971 gtk_widget_set_tooltip_text(button,
4972 _("Pressing this button will reset all modified properties of "
4973 "the selected objects to their current values (the values "
4974 "they have on the server)."));
4975 g_signal_connect(button, "clicked",
4976 G_CALLBACK(property_page_refresh_button_clicked), pp);
4977 gtk_container_add(GTK_CONTAINER(hbox2), button);
4978
4979 button = gtk_button_new_with_mnemonic(_("_Apply"));
4980 gtk_size_group_add_widget(sizegroup, button);
4981 gtk_widget_set_tooltip_text(button,
4982 _("Pressing this button will send all modified properties of "
4983 "the objects selected in the object list to the server. "
4984 "Modified properties' names are shown in red in the properties "
4985 "panel."));
4986 g_signal_connect(button, "clicked",
4987 G_CALLBACK(property_page_apply_button_clicked), pp);
4988 gtk_container_add(GTK_CONTAINER(hbox2), button);
4989
4990 return pp;
4991 }
4992
4993 /****************************************************************************
4994 Returns the translated name of the property page's object type.
4995 ****************************************************************************/
property_page_get_name(const struct property_page * pp)4996 static const char *property_page_get_name(const struct property_page *pp)
4997 {
4998 if (!pp) {
4999 return "";
5000 }
5001 return objtype_get_name(property_page_get_objtype(pp));
5002 }
5003
5004 /****************************************************************************
5005 Returns the object type for this property page, or -1 if none.
5006 ****************************************************************************/
5007 static enum editor_object_type
property_page_get_objtype(const struct property_page * pp)5008 property_page_get_objtype(const struct property_page *pp)
5009 {
5010 if (!pp) {
5011 return -1;
5012 }
5013 return pp->objtype;
5014 }
5015
5016 /****************************************************************************
5017 Create a pixbuf containing an image of the given tile. The image will
5018 only be of the layers containing terrains, resources and specials.
5019
5020 May return NULL on error or bad input.
5021 NB: You must call g_object_unref on the non-NULL return value when you
5022 no longer need it.
5023 ****************************************************************************/
create_tile_pixbuf(const struct tile * ptile)5024 static GdkPixbuf *create_tile_pixbuf(const struct tile *ptile)
5025 {
5026 int layers[] = {
5027 LAYER_BACKGROUND,
5028 LAYER_TERRAIN1,
5029 LAYER_TERRAIN2,
5030 LAYER_TERRAIN3,
5031 LAYER_WATER,
5032 LAYER_ROADS,
5033 LAYER_SPECIAL1,
5034 LAYER_SPECIAL2,
5035 LAYER_SPECIAL3
5036 };
5037 int num_layers = ARRAY_SIZE(layers);
5038
5039 return create_pixbuf_from_layers(ptile, NULL, NULL, layers, num_layers);
5040 }
5041
5042 /****************************************************************************
5043 Create a pixbuf containing an image of the given unit.
5044
5045 May return NULL on error or bad input.
5046 NB: You must call g_object_unref on the non-NULL return value when you
5047 no longer need it.
5048 ****************************************************************************/
create_unit_pixbuf(const struct unit * punit)5049 static GdkPixbuf *create_unit_pixbuf(const struct unit *punit)
5050 {
5051 int layers[] = {
5052 LAYER_UNIT,
5053 LAYER_FOCUS_UNIT,
5054 };
5055 int num_layers = ARRAY_SIZE(layers);
5056
5057 return create_pixbuf_from_layers(NULL, punit, NULL,
5058 layers, num_layers);
5059 }
5060
5061 /****************************************************************************
5062 Create a pixbuf containing an image of the given city.
5063
5064 May return NULL on error or bad input.
5065 NB: You must call g_object_unref on the non-NULL return value when you
5066 no longer need it.
5067 ****************************************************************************/
create_city_pixbuf(const struct city * pcity)5068 static GdkPixbuf *create_city_pixbuf(const struct city *pcity)
5069 {
5070 int layers[] = {
5071 LAYER_BACKGROUND,
5072 LAYER_TERRAIN1,
5073 LAYER_TERRAIN2,
5074 LAYER_TERRAIN3,
5075 LAYER_WATER,
5076 LAYER_ROADS,
5077 LAYER_SPECIAL1,
5078 LAYER_CITY1,
5079 LAYER_SPECIAL2,
5080 LAYER_CITY2,
5081 LAYER_SPECIAL3
5082 };
5083 int num_layers = ARRAY_SIZE(layers);
5084
5085 return create_pixbuf_from_layers(city_tile(pcity), NULL, pcity,
5086 layers, num_layers);
5087 }
5088
5089 /****************************************************************************
5090 Create a pixbuf containing an image of the given tile, unit or city
5091 restricted to the layers listed in 'layers'.
5092
5093 May return NULL on error or bad input.
5094 NB: You must call g_object_unref on the non-NULL return value when you
5095 no longer need it.
5096 ****************************************************************************/
create_pixbuf_from_layers(const struct tile * ptile,const struct unit * punit,const struct city * pcity,int * layers,int num_layers)5097 static GdkPixbuf *create_pixbuf_from_layers(const struct tile *ptile,
5098 const struct unit *punit,
5099 const struct city *pcity,
5100 int *layers,
5101 int num_layers)
5102 {
5103 struct canvas canvas = FC_STATIC_CANVAS_INIT;
5104 int h, i, fh, fw, canvas_x, canvas_y;
5105 GdkPixbuf *pixbuf;
5106 cairo_t *cr;
5107
5108 fw = tileset_full_tile_width(tileset);
5109 fh = tileset_full_tile_height(tileset);
5110 h = tileset_tile_height(tileset);
5111
5112 canvas.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, fw, fh);
5113
5114 cr = cairo_create(canvas.surface);
5115 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
5116 cairo_paint(cr);
5117 cairo_destroy(cr);
5118
5119 canvas_x = 0;
5120 canvas_y = 0;
5121
5122 canvas_y += (fh - h);
5123
5124 for (i = 0; i < num_layers; i++) {
5125 put_one_element(&canvas, 1.0, layers[i],
5126 ptile, NULL, NULL, punit, pcity,
5127 canvas_x, canvas_y, NULL, NULL);
5128 }
5129 pixbuf = surface_get_pixbuf(canvas.surface, fw, fh);
5130 cairo_surface_destroy(canvas.surface);
5131
5132 return pixbuf;
5133 }
5134
5135 /****************************************************************************
5136 Remove all object binds (i.e. objects listed) in the property page.
5137 ****************************************************************************/
property_page_clear_objbinds(struct property_page * pp)5138 static void property_page_clear_objbinds(struct property_page *pp)
5139 {
5140 if (!pp) {
5141 return;
5142 }
5143
5144 gtk_list_store_clear(pp->object_store);
5145 objbind_hash_clear(pp->objbind_table);
5146 property_page_set_focused_objbind(pp, NULL);
5147 }
5148
5149 /****************************************************************************
5150 Create a new object bind to the given object and register it with the
5151 given property page.
5152 ****************************************************************************/
property_page_add_objbind(struct property_page * pp,gpointer object_data)5153 static void property_page_add_objbind(struct property_page *pp,
5154 gpointer object_data)
5155 {
5156 struct objbind *ob;
5157 enum editor_object_type objtype;
5158 int id;
5159
5160 if (!pp) {
5161 return;
5162 }
5163
5164 objtype = property_page_get_objtype(pp);
5165 id = objtype_get_id_from_object(objtype, object_data);
5166 if (id < 0) {
5167 return;
5168 }
5169
5170 if (objbind_hash_lookup(pp->objbind_table, id, NULL)) {
5171 /* Object already exists. */
5172 return;
5173 }
5174
5175 ob = objbind_new(objtype, object_data);
5176 if (!ob) {
5177 return;
5178 }
5179
5180 objbind_bind_properties(ob, pp);
5181
5182 objbind_hash_insert(pp->objbind_table, ob->object_id, ob);
5183 }
5184
5185 /****************************************************************************
5186 Create zero or more object binds from the objects on the given tile to
5187 the properties contained in the given property page.
5188 ****************************************************************************/
property_page_add_objbinds_from_tile(struct property_page * pp,const struct tile * ptile)5189 static void property_page_add_objbinds_from_tile(struct property_page *pp,
5190 const struct tile *ptile)
5191 {
5192
5193 if (!pp || !ptile) {
5194 return;
5195 }
5196
5197 switch (property_page_get_objtype(pp)) {
5198 case OBJTYPE_TILE:
5199 property_page_add_objbind(pp, (gpointer) ptile);
5200 return;
5201
5202 case OBJTYPE_STARTPOS:
5203 {
5204 struct startpos *psp = map_startpos_get(ptile);
5205
5206 if (NULL != psp) {
5207 property_page_add_objbind(pp, map_startpos_get(ptile));
5208 }
5209 }
5210 return;
5211
5212 case OBJTYPE_UNIT:
5213 unit_list_iterate(ptile->units, punit) {
5214 property_page_add_objbind(pp, punit);
5215 } unit_list_iterate_end;
5216 return;
5217
5218 case OBJTYPE_CITY:
5219 if (tile_city(ptile)) {
5220 property_page_add_objbind(pp, tile_city(ptile));
5221 }
5222 return;
5223
5224 case OBJTYPE_PLAYER:
5225 case OBJTYPE_GAME:
5226 return;
5227
5228 case NUM_OBJTYPES:
5229 break;
5230 }
5231
5232 log_error("%s(): Unhandled page object type %s (nb %d).", __FUNCTION__,
5233 objtype_get_name(property_page_get_objtype(pp)),
5234 property_page_get_objtype(pp));
5235 }
5236
5237 /****************************************************************************
5238 Set the column value in the list store of the property page.
5239 Returns TRUE if data was enetered into the store.
5240
5241 NB: This must match the conversion in objprop_get_gtype.
5242 ****************************************************************************/
property_page_set_store_value(struct property_page * pp,struct objprop * op,struct objbind * ob,GtkTreeIter * iter)5243 static bool property_page_set_store_value(struct property_page *pp,
5244 struct objprop *op,
5245 struct objbind *ob,
5246 GtkTreeIter *iter)
5247 {
5248 int col_id;
5249 struct propval *pv;
5250 enum value_types valtype;
5251 char buf[128], *p;
5252 GdkPixbuf *pixbuf = NULL;
5253 GtkListStore *store;
5254 gchar *buf2;
5255
5256 if (!pp || !pp->object_store || !op || !ob) {
5257 return FALSE;
5258 }
5259
5260 if (!objprop_show_in_listview(op)) {
5261 return FALSE;
5262 }
5263
5264 col_id = objprop_get_column_id(op);
5265 if (col_id < 0) {
5266 return FALSE;
5267 }
5268
5269 pv = objbind_get_value_from_object(ob, op);
5270 if (!pv) {
5271 return FALSE;
5272 }
5273
5274 valtype = objprop_get_valtype(op);
5275 store = pp->object_store;
5276
5277 switch (valtype) {
5278 case VALTYPE_NONE:
5279 break;
5280 case VALTYPE_INT:
5281 gtk_list_store_set(store, iter, col_id, pv->data.v_int, -1);
5282 break;
5283 case VALTYPE_BOOL:
5284 /* Set as translated string, not as untranslated G_TYPE_BOOLEAN */
5285 gtk_list_store_set(store, iter, col_id, propval_as_string(pv), -1);
5286 break;
5287 case VALTYPE_STRING:
5288 if (fc_strlcpy(buf, pv->data.v_string, 28) >= 28) {
5289 sz_strlcat(buf, "...");
5290 }
5291 for (p = buf; *p; p++) {
5292 if (*p == '\n' || *p == '\t' || *p == '\r') {
5293 *p = ' ';
5294 }
5295 }
5296 gtk_list_store_set(store, iter, col_id, buf, -1);
5297 break;
5298 case VALTYPE_PIXBUF:
5299 gtk_list_store_set(store, iter, col_id, pv->data.v_pixbuf, -1);
5300 break;
5301 case VALTYPE_BUILT_ARRAY:
5302 case VALTYPE_INVENTIONS_ARRAY:
5303 case VALTYPE_BV_SPECIAL:
5304 case VALTYPE_BV_ROADS:
5305 case VALTYPE_BV_BASES:
5306 case VALTYPE_NATION_HASH:
5307 buf2 = propval_as_string(pv);
5308 gtk_list_store_set(store, iter, col_id, buf2, -1);
5309 g_free(buf2);
5310 break;
5311 case VALTYPE_NATION:
5312 pixbuf = get_flag(pv->data.v_nation);
5313 gtk_list_store_set(store, iter, col_id, pixbuf, -1);
5314 if (pixbuf) {
5315 g_object_unref(pixbuf);
5316 }
5317 break;
5318 case VALTYPE_GOV:
5319 pixbuf = sprite_get_pixbuf(get_government_sprite(tileset, pv->data.v_gov));
5320 gtk_list_store_set(store, iter, col_id, pixbuf, -1);
5321 if (pixbuf) {
5322 g_object_unref(pixbuf);
5323 }
5324 break;
5325 case VALTYPE_TILE_VISION_DATA:
5326 break;
5327 }
5328
5329 propval_free(pv);
5330
5331 return TRUE;
5332 }
5333
5334 /****************************************************************************
5335 Inserts any objbinds owned by this proprety page into the page's list
5336 store if they are not there already and refreshes all property widgets.
5337 ****************************************************************************/
property_page_fill_widgets(struct property_page * pp)5338 static void property_page_fill_widgets(struct property_page *pp)
5339 {
5340 struct objbind *focused;
5341
5342 if (!pp || !pp->objbind_table) {
5343 return;
5344 }
5345
5346 if (pp->object_store) {
5347 GtkTreeIter iter;
5348 GtkTreeRowReference *rr;
5349 GtkTreeModel *model;
5350 GtkTreePath *path;
5351
5352 model = GTK_TREE_MODEL(pp->object_store);
5353
5354 property_page_objbind_iterate(pp, ob) {
5355 if (objbind_get_rowref(ob)) {
5356 continue;
5357 }
5358 gtk_list_store_append(pp->object_store, &iter);
5359 gtk_list_store_set(pp->object_store, &iter, 0, ob, -1);
5360 path = gtk_tree_model_get_path(model, &iter);
5361 rr = gtk_tree_row_reference_new(model, path);
5362 gtk_tree_path_free(path);
5363 objbind_set_rowref(ob, rr);
5364
5365 property_page_objprop_iterate(pp, op) {
5366 property_page_set_store_value(pp, op, ob, &iter);
5367 } property_page_objprop_iterate_end;
5368 } property_page_objbind_iterate_end;
5369
5370 if (gtk_tree_model_get_iter_first(model, &iter)) {
5371 GtkTreeSelection *sel;
5372 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5373 gtk_tree_selection_select_iter(sel, &iter);
5374 }
5375 }
5376
5377 focused = property_page_get_focused_objbind(pp);
5378 property_page_objprop_iterate(pp, op) {
5379 objprop_refresh_widget(op, focused);
5380 } property_page_objprop_iterate_end;
5381 }
5382
5383 /****************************************************************************
5384 Get the objbind corresponding to the object that is currently in view
5385 (i.e. in the information/properties panels) or NULL if none.
5386 ****************************************************************************/
property_page_get_focused_objbind(struct property_page * pp)5387 static struct objbind *property_page_get_focused_objbind(struct property_page *pp)
5388 {
5389 if (!pp) {
5390 return NULL;
5391 }
5392 return pp->focused_objbind;
5393 }
5394
5395 /****************************************************************************
5396 Set the objbind that should be shown in the properties panel. Does not
5397 refresh property widgets.
5398 ****************************************************************************/
property_page_set_focused_objbind(struct property_page * pp,struct objbind * ob)5399 static void property_page_set_focused_objbind(struct property_page *pp,
5400 struct objbind *ob)
5401 {
5402 if (!pp) {
5403 return;
5404 }
5405 pp->focused_objbind = ob;
5406 }
5407
5408 /****************************************************************************
5409 Returns the objbind whose object corresponds to the given id, or NULL
5410 if no such objbind exists.
5411 ****************************************************************************/
property_page_get_objbind(struct property_page * pp,int object_id)5412 static struct objbind *property_page_get_objbind(struct property_page *pp,
5413 int object_id)
5414 {
5415 struct objbind *ob;
5416
5417 if (!pp || !pp->objbind_table) {
5418 return NULL;
5419 }
5420
5421 objbind_hash_lookup(pp->objbind_table, object_id, &ob);
5422 return ob;
5423 }
5424
5425 /****************************************************************************
5426 Removes all of the current objbinds and extracts new ones from the
5427 supplied list of tiles.
5428 ****************************************************************************/
property_page_load_tiles(struct property_page * pp,const struct tile_list * tiles)5429 static void property_page_load_tiles(struct property_page *pp,
5430 const struct tile_list *tiles)
5431 {
5432 if (!pp || !tiles) {
5433 return;
5434 }
5435
5436 tile_list_iterate(tiles, ptile) {
5437 property_page_add_objbinds_from_tile(pp, ptile);
5438 } tile_list_iterate_end;
5439
5440 property_page_fill_widgets(pp);
5441 }
5442
5443 /****************************************************************************
5444 Return the number of current bound objects to this property page.
5445 ****************************************************************************/
property_page_get_num_objbinds(const struct property_page * pp)5446 static int property_page_get_num_objbinds(const struct property_page *pp)
5447 {
5448 if (!pp || !pp->objbind_table) {
5449 return 0;
5450 }
5451 return objbind_hash_size(pp->objbind_table);
5452 }
5453
5454 /****************************************************************************
5455 Called when a user sets a new value for the given property via the GUI.
5456 Refreshes the properties widget if anything changes.
5457 ****************************************************************************/
property_page_change_value(struct property_page * pp,struct objprop * op,struct propval * pv)5458 static void property_page_change_value(struct property_page *pp,
5459 struct objprop *op,
5460 struct propval *pv)
5461 {
5462 GtkTreeSelection *sel;
5463 GtkTreeModel *model;
5464 GList *rows, *p;
5465 GtkTreePath *path;
5466 GtkTreeIter iter;
5467 struct objbind *ob;
5468
5469 if (!pp || !op || !pp->object_view) {
5470 return;
5471 }
5472
5473 if (objprop_is_readonly(op)) {
5474 return;
5475 }
5476
5477 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5478 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5479
5480 for (p = rows; p != NULL; p = p->next) {
5481 path = p->data;
5482 if (gtk_tree_model_get_iter(model, &iter, path)) {
5483 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5484 objbind_set_modified_value(ob, op, pv);
5485 }
5486 gtk_tree_path_free(path);
5487 }
5488 g_list_free(rows);
5489
5490 ob = property_page_get_focused_objbind(pp);
5491 objprop_refresh_widget(op, ob);
5492 }
5493
5494 /****************************************************************************
5495 Send all modified values of all selected properties.
5496 ****************************************************************************/
property_page_send_values(struct property_page * pp)5497 static void property_page_send_values(struct property_page *pp)
5498 {
5499 GtkTreeSelection *sel;
5500 GtkTreeModel *model;
5501 GList *rows, *p;
5502 GtkTreePath *path;
5503 GtkTreeIter iter;
5504 struct objbind *ob;
5505 union packetdata packet;
5506 struct connection *my_conn = &client.conn;
5507
5508 if (!pp || !pp->object_view) {
5509 return;
5510 }
5511
5512 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5513 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5514 return;
5515 }
5516
5517 packet = property_page_new_packet(pp);
5518 if (!packet.pointers.v_pointer1) {
5519 return;
5520 }
5521
5522 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5523 connection_do_buffer(my_conn);
5524 for (p = rows; p != NULL; p = p->next) {
5525 path = p->data;
5526 if (gtk_tree_model_get_iter(model, &iter, path)) {
5527 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5528 if (objbind_has_modified_properties(ob)) {
5529 objbind_pack_current_values(ob, packet);
5530 property_page_objprop_iterate(pp, op) {
5531 if (objprop_is_readonly(op)) {
5532 continue;
5533 }
5534 objbind_pack_modified_value(ob, op, packet);
5535 } property_page_objprop_iterate_end;
5536 property_page_send_packet(pp, packet);
5537 }
5538 }
5539 gtk_tree_path_free(path);
5540 }
5541 connection_do_unbuffer(my_conn);
5542 g_list_free(rows);
5543
5544 property_page_free_packet(pp, packet);
5545 }
5546
5547 /****************************************************************************
5548 Returns pointer to a packet suitable for this page's object type. Result
5549 should be freed using property_page_free_packet when no longer needed.
5550 ****************************************************************************/
property_page_new_packet(struct property_page * pp)5551 static union packetdata property_page_new_packet(struct property_page *pp)
5552 {
5553 union packetdata packet;
5554
5555 packet.pointers.v_pointer2 = NULL;
5556
5557 if (!pp) {
5558 packet.pointers.v_pointer1 = NULL;
5559 return packet;
5560 }
5561
5562 switch (property_page_get_objtype(pp)) {
5563 case OBJTYPE_TILE:
5564 packet.tile = fc_calloc(1, sizeof(*packet.tile));
5565 break;
5566 case OBJTYPE_STARTPOS:
5567 packet.startpos = fc_calloc(1, sizeof(*packet.startpos));
5568 break;
5569 case OBJTYPE_UNIT:
5570 packet.unit = fc_calloc(1, sizeof(*packet.unit));
5571 break;
5572 case OBJTYPE_CITY:
5573 packet.city = fc_calloc(1, sizeof(*packet.city));
5574 break;
5575 case OBJTYPE_PLAYER:
5576 packet.player = fc_calloc(1, sizeof(*packet.player));
5577 break;
5578 case OBJTYPE_GAME:
5579 packet.game.game = fc_calloc(1, sizeof(*packet.game.game));
5580 packet.game.desc = fc_calloc(1, sizeof(*packet.game.desc));
5581 break;
5582 case NUM_OBJTYPES:
5583 break;
5584 }
5585
5586 return packet;
5587 }
5588
5589 /****************************************************************************
5590 Sends the given packet.
5591 ****************************************************************************/
property_page_send_packet(struct property_page * pp,union packetdata packet)5592 static void property_page_send_packet(struct property_page *pp,
5593 union packetdata packet)
5594 {
5595 struct connection *my_conn = &client.conn;
5596
5597 if (!pp || !packet.pointers.v_pointer1) {
5598 return;
5599 }
5600
5601 switch (property_page_get_objtype(pp)) {
5602 case OBJTYPE_TILE:
5603 send_packet_edit_tile(my_conn, packet.tile);
5604 return;
5605 case OBJTYPE_STARTPOS:
5606 send_packet_edit_startpos_full(my_conn, packet.startpos);
5607 return;
5608 case OBJTYPE_UNIT:
5609 send_packet_edit_unit(my_conn, packet.unit);
5610 return;
5611 case OBJTYPE_CITY:
5612 send_packet_edit_city(my_conn, packet.city);
5613 return;
5614 case OBJTYPE_PLAYER:
5615 send_packet_edit_player(my_conn, packet.player);
5616 return;
5617 case OBJTYPE_GAME:
5618 send_packet_edit_game(my_conn, packet.game.game);
5619 send_packet_edit_scenario_desc(my_conn, packet.game.desc);
5620 return;
5621 case NUM_OBJTYPES:
5622 break;
5623 }
5624
5625 log_error("%s(): Unhandled object type %s (nb %d).",
5626 __FUNCTION__, objtype_get_name(property_page_get_objtype(pp)),
5627 property_page_get_objtype(pp));
5628 }
5629
5630 /****************************************************************************
5631 Free any resources being used by the packet.
5632 ****************************************************************************/
property_page_free_packet(struct property_page * pp,union packetdata packet)5633 static void property_page_free_packet(struct property_page *pp,
5634 union packetdata packet)
5635 {
5636 if (!packet.pointers.v_pointer1) {
5637 return;
5638 }
5639
5640 free(packet.pointers.v_pointer1);
5641 packet.pointers.v_pointer1 = NULL;
5642
5643 if (packet.pointers.v_pointer2 != NULL) {
5644 free(packet.pointers.v_pointer2);
5645 packet.pointers.v_pointer2 = NULL;
5646 }
5647 }
5648
5649 /****************************************************************************
5650 Reload the displayed values of all properties for the selected bound
5651 objects. Hence, deletes all their stored modified values.
5652 ****************************************************************************/
property_page_reset_objbinds(struct property_page * pp)5653 static void property_page_reset_objbinds(struct property_page *pp)
5654 {
5655 GtkTreeSelection *sel;
5656 GtkTreeModel *model;
5657 GtkTreeIter iter;
5658 GtkTreePath *path;
5659 GList *rows, *p;
5660 struct objbind *ob;
5661
5662 if (!pp || !pp->object_view) {
5663 return;
5664 }
5665
5666 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5667 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5668 return;
5669 }
5670
5671 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5672 for (p = rows; p != NULL; p = p->next) {
5673 path = p->data;
5674 if (gtk_tree_model_get_iter(model, &iter, path)) {
5675 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5676 objbind_clear_all_modified_values(ob);
5677 property_page_objprop_iterate(pp, op) {
5678 property_page_set_store_value(pp, op, ob, &iter);
5679 } property_page_objprop_iterate_end;
5680 }
5681 gtk_tree_path_free(path);
5682 }
5683 g_list_free(rows);
5684
5685 ob = property_page_get_focused_objbind(pp);
5686 property_page_objprop_iterate(pp, op) {
5687 objprop_refresh_widget(op, ob);
5688 } property_page_objprop_iterate_end;
5689 }
5690
5691 /****************************************************************************
5692 Destroy all selected objects in the current property page.
5693 ****************************************************************************/
property_page_destroy_objects(struct property_page * pp)5694 static void property_page_destroy_objects(struct property_page *pp)
5695 {
5696 GtkTreeSelection *sel;
5697 GtkTreeModel *model;
5698 GtkTreeIter iter;
5699 GtkTreePath *path;
5700 GList *rows, *p;
5701 struct objbind *ob;
5702 struct connection *my_conn = &client.conn;
5703
5704 if (!pp || !pp->object_view) {
5705 return;
5706 }
5707
5708 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pp->object_view));
5709 if (gtk_tree_selection_count_selected_rows(sel) < 1) {
5710 return;
5711 }
5712
5713 rows = gtk_tree_selection_get_selected_rows(sel, &model);
5714 connection_do_buffer(my_conn);
5715 for (p = rows; p != NULL; p = p->next) {
5716 path = p->data;
5717 if (gtk_tree_model_get_iter(model, &iter, path)) {
5718 gtk_tree_model_get(model, &iter, 0, &ob, -1);
5719 objbind_request_destroy_object(ob);
5720 }
5721 gtk_tree_path_free(path);
5722 }
5723 connection_do_unbuffer(my_conn);
5724 g_list_free(rows);
5725 }
5726
5727 /****************************************************************************
5728 Create objects corresponding to the type of this property page. Parameters
5729 such as the type, count, size and player owner are taken from the current
5730 editor state. The 'hint_tiles' argument is a list of tiles where the
5731 objects could be created.
5732 ****************************************************************************/
property_page_create_objects(struct property_page * pp,struct tile_list * hint_tiles)5733 static void property_page_create_objects(struct property_page *pp,
5734 struct tile_list *hint_tiles)
5735 {
5736 enum editor_object_type objtype;
5737 int apno, value, count, size;
5738 int tag;
5739 struct connection *my_conn = &client.conn;
5740 struct tile *ptile = NULL;
5741 struct player *pplayer;
5742
5743 if (!pp) {
5744 return;
5745 }
5746
5747 objtype = property_page_get_objtype(pp);
5748 if (objtype_is_conserved(objtype)) {
5749 return;
5750 }
5751
5752 tag = get_next_unique_tag();
5753 count = 1;
5754
5755 switch (objtype) {
5756 case OBJTYPE_STARTPOS:
5757 if (hint_tiles) {
5758 tile_list_iterate(hint_tiles, atile) {
5759 if (NULL == map_startpos_get(atile)) {
5760 ptile = atile;
5761 break;
5762 }
5763 } tile_list_iterate_end;
5764 }
5765
5766 if (NULL == ptile) {
5767 ptile = get_center_tile_mapcanvas();
5768 }
5769
5770 if (NULL == ptile) {
5771 break;
5772 }
5773
5774 dsend_packet_edit_startpos(my_conn, tile_index(ptile), FALSE, tag);
5775 break;
5776
5777 case OBJTYPE_UNIT:
5778 if (hint_tiles) {
5779 tile_list_iterate(hint_tiles, atile) {
5780 if (can_create_unit_at_tile(atile)) {
5781 ptile = atile;
5782 break;
5783 }
5784 } tile_list_iterate_end;
5785 }
5786
5787 if (!ptile) {
5788 struct unit *punit;
5789 property_page_objbind_iterate(pp, ob) {
5790 punit = objbind_get_object(ob);
5791 if (punit && can_create_unit_at_tile(unit_tile(punit))) {
5792 ptile = unit_tile(punit);
5793 break;
5794 }
5795 } property_page_objbind_iterate_end;
5796 }
5797
5798 if (!ptile) {
5799 ptile = get_center_tile_mapcanvas();
5800 }
5801
5802 if (!ptile) {
5803 break;
5804 }
5805
5806 apno = editor_tool_get_applied_player(ETT_UNIT);
5807 count = editor_tool_get_count(ETT_UNIT);
5808 value = editor_tool_get_value(ETT_UNIT);
5809 dsend_packet_edit_unit_create(my_conn, apno, tile_index(ptile),
5810 value, count, tag);
5811 break;
5812
5813 case OBJTYPE_CITY:
5814 apno = editor_tool_get_applied_player(ETT_CITY);
5815 pplayer = player_by_number(apno);
5816 if (pplayer && hint_tiles) {
5817 tile_list_iterate(hint_tiles, atile) {
5818 if (!is_enemy_unit_tile(atile, pplayer)
5819 && city_can_be_built_here(atile, NULL)) {
5820 ptile = atile;
5821 break;
5822 }
5823 } tile_list_iterate_end;
5824 }
5825
5826 if (!ptile) {
5827 ptile = get_center_tile_mapcanvas();
5828 }
5829
5830 if (!ptile) {
5831 break;
5832 }
5833
5834 size = editor_tool_get_size(ETT_CITY);
5835 dsend_packet_edit_city_create(my_conn, apno, tile_index(ptile),
5836 size, tag);
5837 break;
5838
5839 case OBJTYPE_PLAYER:
5840 dsend_packet_edit_player_create(my_conn, tag);
5841 break;
5842
5843 case OBJTYPE_TILE:
5844 case OBJTYPE_GAME:
5845 case NUM_OBJTYPES:
5846 break;
5847 }
5848
5849 property_page_store_creation_tag(pp, tag, count);
5850 }
5851
5852 /****************************************************************************
5853 Update objbinds and widgets according to how the object given by
5854 'object_id' has changed. If the object no longer exists then the
5855 objbind is removed from the property page.
5856 ****************************************************************************/
property_page_object_changed(struct property_page * pp,int object_id,bool removed)5857 static void property_page_object_changed(struct property_page *pp,
5858 int object_id,
5859 bool removed)
5860 {
5861 struct objbind *ob;
5862 GtkTreeRowReference *rr;
5863
5864 ob = property_page_get_objbind(pp, object_id);
5865 if (!ob) {
5866 return;
5867 }
5868
5869 rr = objbind_get_rowref(ob);
5870 if (rr && gtk_tree_row_reference_valid(rr)) {
5871 GtkTreePath *path;
5872 GtkTreeIter iter;
5873 GtkTreeModel *model;
5874
5875 model = GTK_TREE_MODEL(pp->object_store);
5876 path = gtk_tree_row_reference_get_path(rr);
5877
5878 if (gtk_tree_model_get_iter(model, &iter, path)) {
5879 if (removed) {
5880 gtk_list_store_remove(pp->object_store, &iter);
5881 } else {
5882 property_page_objprop_iterate(pp, op) {
5883 property_page_set_store_value(pp, op, ob, &iter);
5884 } property_page_objprop_iterate_end;
5885 }
5886 }
5887
5888 gtk_tree_path_free(path);
5889 }
5890
5891 if (removed) {
5892 objbind_hash_remove(pp->objbind_table, object_id);
5893 return;
5894 }
5895
5896 if (ob == property_page_get_focused_objbind(pp)) {
5897 property_page_objprop_iterate(pp, op) {
5898 objprop_refresh_widget(op, ob);
5899 } property_page_objprop_iterate_end;
5900 }
5901 }
5902
5903 /****************************************************************************
5904 Handle a notification of object creation sent back from the server. If
5905 this is something we previously requested, then 'tag' should be found in
5906 the tag table. In this case we create a new objbind for the object given
5907 by 'object_id' and add it to this page.
5908 ****************************************************************************/
property_page_object_created(struct property_page * pp,int tag,int object_id)5909 static void property_page_object_created(struct property_page *pp,
5910 int tag, int object_id)
5911 {
5912 gpointer object;
5913 enum editor_object_type objtype;
5914
5915 if (!property_page_tag_is_known(pp, tag)) {
5916 return;
5917 }
5918 property_page_remove_creation_tag(pp, tag);
5919
5920 objtype = property_page_get_objtype(pp);
5921 object = objtype_get_object_from_id(objtype, object_id);
5922
5923 if (!object) {
5924 return;
5925 }
5926
5927 property_page_add_objbind(pp, object);
5928 property_page_fill_widgets(pp);
5929 }
5930
5931 /****************************************************************************
5932 Add the extviewer's view widget to the property page so that it can
5933 be shown in the extended property view panel.
5934 ****************************************************************************/
property_page_add_extviewer(struct property_page * pp,struct extviewer * ev)5935 static void property_page_add_extviewer(struct property_page *pp,
5936 struct extviewer *ev)
5937 {
5938 GtkWidget *w;
5939
5940 if (!pp || !ev) {
5941 return;
5942 }
5943
5944 w = extviewer_get_view_widget(ev);
5945 if (!w) {
5946 return;
5947 }
5948 gtk_notebook_append_page(GTK_NOTEBOOK(pp->extviewer_notebook), w, NULL);
5949 }
5950
5951 /****************************************************************************
5952 Make the given extended property viewer's view widget visible in the
5953 property page.
5954 ****************************************************************************/
property_page_show_extviewer(struct property_page * pp,struct extviewer * ev)5955 static void property_page_show_extviewer(struct property_page *pp,
5956 struct extviewer *ev)
5957 {
5958 GtkWidget *w;
5959 GtkNotebook *notebook;
5960 int page;
5961
5962 if (!pp || !ev) {
5963 return;
5964 }
5965
5966 w = extviewer_get_view_widget(ev);
5967 if (!w) {
5968 return;
5969 }
5970
5971 notebook = GTK_NOTEBOOK(pp->extviewer_notebook);
5972 page = gtk_notebook_page_num(notebook, w);
5973 gtk_notebook_set_current_page(notebook, page);
5974 }
5975
5976 /****************************************************************************
5977 Store the given object creation tag so that when the server notifies
5978 us about it we know what to do, up to 'count' times.
5979 ****************************************************************************/
property_page_store_creation_tag(struct property_page * pp,int tag,int count)5980 static void property_page_store_creation_tag(struct property_page *pp,
5981 int tag, int count)
5982 {
5983 if (!pp || !pp->tag_table) {
5984 return;
5985 }
5986
5987 if (stored_tag_hash_lookup(pp->tag_table, tag, NULL)) {
5988 log_error("Attempted to insert object creation tag %d "
5989 "twice into tag table for property page %p (%d %s).",
5990 tag, pp, property_page_get_objtype(pp),
5991 property_page_get_name(pp));
5992 return;
5993 }
5994
5995 stored_tag_hash_insert(pp->tag_table, tag, count);
5996 }
5997
5998 /****************************************************************************
5999 Decrease the tag count and remove the object creation tag if it is no
6000 longer needed.
6001 ****************************************************************************/
property_page_remove_creation_tag(struct property_page * pp,int tag)6002 static void property_page_remove_creation_tag(struct property_page *pp,
6003 int tag)
6004 {
6005 int count;
6006
6007 if (!pp || !pp->tag_table) {
6008 return;
6009 }
6010
6011 if (stored_tag_hash_lookup(pp->tag_table, tag, &count)) {
6012 if (0 >= --count) {
6013 stored_tag_hash_remove(pp->tag_table, tag);
6014 }
6015 }
6016 }
6017
6018 /****************************************************************************
6019 Check if the given tag is one that we previously stored.
6020 ****************************************************************************/
property_page_tag_is_known(struct property_page * pp,int tag)6021 static bool property_page_tag_is_known(struct property_page *pp, int tag)
6022 {
6023 if (!pp || !pp->tag_table) {
6024 return FALSE;
6025 }
6026 return stored_tag_hash_lookup(pp->tag_table, tag, NULL);
6027 }
6028
6029 /****************************************************************************
6030 Remove all tags in the tag table.
6031 ****************************************************************************/
property_page_clear_tags(struct property_page * pp)6032 static void property_page_clear_tags(struct property_page *pp)
6033 {
6034 if (!pp || !pp->tag_table) {
6035 return;
6036 }
6037 stored_tag_hash_clear(pp->tag_table);
6038 }
6039
6040 /****************************************************************************
6041 Handles the 'clicked' signal for the "Apply" button in the property page.
6042 ****************************************************************************/
property_page_apply_button_clicked(GtkButton * button,gpointer userdata)6043 static void property_page_apply_button_clicked(GtkButton *button,
6044 gpointer userdata)
6045 {
6046 struct property_page *pp = userdata;
6047 property_page_send_values(pp);
6048 }
6049
6050 /****************************************************************************
6051 Handles the 'clicked' signal for the "Refresh" button in the
6052 property page.
6053 ****************************************************************************/
property_page_refresh_button_clicked(GtkButton * button,gpointer userdata)6054 static void property_page_refresh_button_clicked(GtkButton *button,
6055 gpointer userdata)
6056 {
6057 struct property_page *pp = userdata;
6058 property_page_reset_objbinds(pp);
6059 }
6060
6061 /****************************************************************************
6062 Handle a request to create a new object in the property page.
6063 ****************************************************************************/
property_page_create_button_clicked(GtkButton * button,gpointer userdata)6064 static void property_page_create_button_clicked(GtkButton *button,
6065 gpointer userdata)
6066 {
6067 struct property_page *pp = userdata, *tile_pp;
6068 struct tile_list *tiles = NULL;
6069 struct tile *ptile;
6070
6071 if (!pp) {
6072 return;
6073 }
6074
6075 tile_pp = property_editor_get_page(pp->pe_parent, OBJTYPE_TILE);
6076 tiles = tile_list_new();
6077
6078 property_page_objbind_iterate(tile_pp, ob) {
6079 ptile = objbind_get_object(ob);
6080 if (ptile) {
6081 tile_list_append(tiles, ptile);
6082 }
6083 } property_page_objbind_iterate_end;
6084
6085 property_page_create_objects(pp, tiles);
6086 tile_list_destroy(tiles);
6087 }
6088
6089 /****************************************************************************
6090 Handle a click on the "destroy" button.
6091 ****************************************************************************/
property_page_destroy_button_clicked(GtkButton * button,gpointer userdata)6092 static void property_page_destroy_button_clicked(GtkButton *button,
6093 gpointer userdata)
6094 {
6095 struct property_page *pp = userdata;
6096 property_page_destroy_objects(pp);
6097 }
6098
6099 /****************************************************************************
6100 Create and add a property page for the given object type
6101 to the property editor. Returns TRUE if successful.
6102 ****************************************************************************/
property_editor_add_page(struct property_editor * pe,enum editor_object_type objtype)6103 static bool property_editor_add_page(struct property_editor *pe,
6104 enum editor_object_type objtype)
6105 {
6106 struct property_page *pp;
6107 GtkWidget *label;
6108 const char *name;
6109
6110 if (!pe || !pe->notebook) {
6111 return FALSE;
6112 }
6113
6114 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
6115 return FALSE;
6116 }
6117
6118 pp = property_page_new(objtype, pe);
6119 if (!pp) {
6120 return FALSE;
6121 }
6122
6123 name = property_page_get_name(pp);
6124 label = gtk_label_new(name);
6125 gtk_notebook_append_page(GTK_NOTEBOOK(pe->notebook),
6126 pp->widget, label);
6127
6128 pe->property_pages[objtype] = pp;
6129
6130 return TRUE;
6131 }
6132
6133 /****************************************************************************
6134 Returns the property page for the given object type.
6135 ****************************************************************************/
6136 static struct property_page *
property_editor_get_page(struct property_editor * pe,enum editor_object_type objtype)6137 property_editor_get_page(struct property_editor *pe,
6138 enum editor_object_type objtype)
6139 {
6140 if (!pe || !(0 <= objtype && objtype < NUM_OBJTYPES)) {
6141 return NULL;
6142 }
6143
6144 return pe->property_pages[objtype];
6145 }
6146
6147 /****************************************************************************
6148 Create and return the property editor widget bundle.
6149 ****************************************************************************/
property_editor_new(void)6150 static struct property_editor *property_editor_new(void)
6151 {
6152 struct property_editor *pe;
6153 GtkWidget *win, *notebook, *vbox;
6154 enum editor_object_type objtype;
6155
6156 pe = fc_calloc(1, sizeof(*pe));
6157
6158 /* The property editor dialog window. */
6159
6160 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6161 gtk_window_set_title(GTK_WINDOW(win), _("Property Editor"));
6162 gtk_window_set_resizable(GTK_WINDOW(win), TRUE);
6163 gtk_window_set_default_size(GTK_WINDOW(win), 780, 560);
6164 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
6165 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(toplevel));
6166 gtk_window_set_destroy_with_parent(GTK_WINDOW(win), TRUE);
6167 gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG);
6168 gtk_container_set_border_width(GTK_CONTAINER(win), 4);
6169 g_signal_connect(win, "delete-event",
6170 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
6171 pe->widget = win;
6172
6173 vbox = gtk_grid_new();
6174 gtk_container_add(GTK_CONTAINER(win), vbox);
6175
6176 /* Property pages. */
6177
6178 notebook = gtk_notebook_new();
6179 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), TRUE);
6180 gtk_container_add(GTK_CONTAINER(vbox), notebook);
6181 pe->notebook = notebook;
6182
6183 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6184 property_editor_add_page(pe, objtype);
6185 }
6186
6187 return pe;
6188 }
6189
6190 /****************************************************************************
6191 Get the property editor for the client's GUI.
6192 ****************************************************************************/
editprop_get_property_editor(void)6193 struct property_editor *editprop_get_property_editor(void)
6194 {
6195 if (!the_property_editor) {
6196 the_property_editor = property_editor_new();
6197 }
6198 return the_property_editor;
6199 }
6200
6201 /****************************************************************************
6202 Refresh the given property editor according to the given list of tiles.
6203 ****************************************************************************/
property_editor_load_tiles(struct property_editor * pe,const struct tile_list * tiles)6204 void property_editor_load_tiles(struct property_editor *pe,
6205 const struct tile_list *tiles)
6206 {
6207 struct property_page *pp;
6208 enum editor_object_type objtype;
6209 int i;
6210 const enum editor_object_type preferred[] = {
6211 OBJTYPE_CITY,
6212 OBJTYPE_UNIT,
6213 OBJTYPE_STARTPOS,
6214 OBJTYPE_TILE
6215 };
6216
6217 if (!pe || !tiles) {
6218 return;
6219 }
6220
6221 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6222 pp = property_editor_get_page(pe, objtype);
6223 property_page_load_tiles(pp, tiles);
6224 }
6225
6226 for (i = 0; i < ARRAY_SIZE(preferred) - 1; i++) {
6227 pp = property_editor_get_page(pe, preferred[i]);
6228 if (property_page_get_num_objbinds(pp) > 0) {
6229 break;
6230 }
6231 }
6232 objtype = preferred[i];
6233 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6234 }
6235
6236 /****************************************************************************
6237 Show the property editor to the user, with given page corresponding to
6238 'objtype' in front (if a valid object type).
6239 ****************************************************************************/
property_editor_popup(struct property_editor * pe,enum editor_object_type objtype)6240 void property_editor_popup(struct property_editor *pe,
6241 enum editor_object_type objtype)
6242 {
6243 if (!pe || !pe->widget) {
6244 return;
6245 }
6246
6247 gtk_widget_show_all(pe->widget);
6248
6249 gtk_window_present(GTK_WINDOW(pe->widget));
6250 if (0 <= objtype && objtype < NUM_OBJTYPES) {
6251 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6252 }
6253 }
6254
6255 /****************************************************************************
6256 Hide the property editor window.
6257 ****************************************************************************/
property_editor_popdown(struct property_editor * pe)6258 void property_editor_popdown(struct property_editor *pe)
6259 {
6260 if (!pe || !pe->widget) {
6261 return;
6262 }
6263 gtk_widget_hide(pe->widget);
6264 }
6265
6266 /****************************************************************************
6267 Handle a notification from the client core that some object has changed
6268 state at the server side (including being removed).
6269 ****************************************************************************/
property_editor_handle_object_changed(struct property_editor * pe,enum editor_object_type objtype,int object_id,bool remove)6270 void property_editor_handle_object_changed(struct property_editor *pe,
6271 enum editor_object_type objtype,
6272 int object_id,
6273 bool remove)
6274 {
6275 struct property_page *pp;
6276
6277 if (!pe) {
6278 return;
6279 }
6280
6281 if (!(0 <= objtype && objtype < NUM_OBJTYPES)) {
6282 return;
6283 }
6284
6285 pp = property_editor_get_page(pe, objtype);
6286 property_page_object_changed(pp, object_id, remove);
6287 }
6288
6289 /****************************************************************************
6290 Handle a notification that an object was created under the given tag.
6291 ****************************************************************************/
property_editor_handle_object_created(struct property_editor * pe,int tag,int object_id)6292 void property_editor_handle_object_created(struct property_editor *pe,
6293 int tag, int object_id)
6294 {
6295 enum editor_object_type objtype;
6296 struct property_page *pp;
6297
6298 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6299 if (objtype_is_conserved(objtype)) {
6300 continue;
6301 }
6302 pp = property_editor_get_page(pe, objtype);
6303 property_page_object_created(pp, tag, object_id);
6304 }
6305 }
6306
6307 /****************************************************************************
6308 Clear all property pages in the given property editor.
6309 ****************************************************************************/
property_editor_clear(struct property_editor * pe)6310 void property_editor_clear(struct property_editor *pe)
6311 {
6312 enum editor_object_type objtype;
6313 struct property_page *pp;
6314
6315 if (!pe) {
6316 return;
6317 }
6318
6319 for (objtype = 0; objtype < NUM_OBJTYPES; objtype++) {
6320 pp = property_editor_get_page(pe, objtype);
6321 property_page_clear_objbinds(pp);
6322 property_page_clear_tags(pp);
6323 }
6324 }
6325
6326 /****************************************************************************
6327 Clear and load objects into the property page corresponding to the given
6328 object type. Also, make it the current shown notebook page.
6329 ****************************************************************************/
property_editor_reload(struct property_editor * pe,enum editor_object_type objtype)6330 void property_editor_reload(struct property_editor *pe,
6331 enum editor_object_type objtype)
6332 {
6333 struct property_page *pp;
6334
6335 if (!pe) {
6336 return;
6337 }
6338
6339 pp = property_editor_get_page(pe, objtype);
6340 if (!pp) {
6341 return;
6342 }
6343
6344 property_page_clear_objbinds(pp);
6345
6346 switch (objtype) {
6347 case OBJTYPE_PLAYER:
6348 players_iterate(pplayer) {
6349 property_page_add_objbind(pp, pplayer);
6350 } players_iterate_end;
6351 break;
6352 case OBJTYPE_GAME:
6353 property_page_add_objbind(pp, &game);
6354 break;
6355 case OBJTYPE_TILE:
6356 case OBJTYPE_STARTPOS:
6357 case OBJTYPE_UNIT:
6358 case OBJTYPE_CITY:
6359 case NUM_OBJTYPES:
6360 break;
6361 }
6362
6363 property_page_fill_widgets(pp);
6364 gtk_notebook_set_current_page(GTK_NOTEBOOK(pe->notebook), objtype);
6365 }
6366
6367 /****************************************************************************
6368 Create a new property filter from the given filter string. Result
6369 should be freed by property_filter_free when no longed needed.
6370
6371 The filter string is '|' ("or") separated list of '&' ("and") separated
6372 lists of patterns. A pattern may be preceeded by '!' to have its result
6373 negated.
6374
6375 NB: If you change the behaviour of this function, be sure to update
6376 the filter tooltip in property_page_new().
6377 ****************************************************************************/
property_filter_new(const char * filter)6378 static struct property_filter *property_filter_new(const char *filter)
6379 {
6380 struct property_filter *pf;
6381 struct pf_conjunction *pfc;
6382 struct pf_pattern *pfp;
6383 int or_clause_count, and_clause_count;
6384 char *or_clauses[PF_MAX_CLAUSES], *and_clauses[PF_MAX_CLAUSES];
6385 const char *pattern;
6386 int i, j;
6387
6388 pf = fc_calloc(1, sizeof(*pf));
6389
6390 if (!filter || filter[0] == '\0') {
6391 return pf;
6392 }
6393
6394 or_clause_count = get_tokens(filter, or_clauses,
6395 PF_MAX_CLAUSES,
6396 PF_DISJUNCTION_SEPARATOR);
6397
6398 for (i = 0; i < or_clause_count; i++) {
6399 if (or_clauses[i][0] == '\0') {
6400 continue;
6401 }
6402 pfc = &pf->disjunction[pf->count];
6403
6404 and_clause_count = get_tokens(or_clauses[i], and_clauses,
6405 PF_MAX_CLAUSES,
6406 PF_CONJUNCTION_SEPARATOR);
6407
6408 for (j = 0; j < and_clause_count; j++) {
6409 if (and_clauses[j][0] == '\0') {
6410 continue;
6411 }
6412 pfp = &pfc->conjunction[pfc->count];
6413 pattern = and_clauses[j];
6414
6415 switch (pattern[0]) {
6416 case '!':
6417 pfp->negate = TRUE;
6418 pfp->text = fc_strdup(pattern + 1);
6419 break;
6420 default:
6421 pfp->text = fc_strdup(pattern);
6422 break;
6423 }
6424 pfc->count++;
6425 }
6426 free_tokens(and_clauses, and_clause_count);
6427 pf->count++;
6428 }
6429
6430 free_tokens(or_clauses, or_clause_count);
6431
6432 return pf;
6433 }
6434
6435 /****************************************************************************
6436 Returns TRUE if the filter matches the given object property.
6437
6438 The filter matches if its truth value is TRUE. That is, it has at least
6439 one OR clause in which all AND clauses are TRUE. An AND clause is TRUE
6440 if its pattern matches the name of the given object property (case is
6441 ignored), or it is negated and does not match. For example:
6442
6443 a - Matches all properties whose names contain "a" (or "A").
6444 !a - Matches all properties whose names do not contain "a".
6445 a|b - Matches all properties whose names contain "a" or "b".
6446 a|b&c - Matches all properties whose names contain either an "a",
6447 or contain both "b" and "c".
6448
6449 NB: If you change the behaviour of this function, be sure to update
6450 the filter tooltip in property_page_new().
6451 ****************************************************************************/
property_filter_match(struct property_filter * pf,const struct objprop * op)6452 static bool property_filter_match(struct property_filter *pf,
6453 const struct objprop *op)
6454 {
6455 struct pf_pattern *pfp;
6456 struct pf_conjunction *pfc;
6457 const char *name;
6458 bool match, or_result, and_result;
6459 int i, j;
6460
6461 if (!pf) {
6462 return TRUE;
6463 }
6464 if (!op) {
6465 return FALSE;
6466 }
6467
6468 name = objprop_get_name(op);
6469 if (!name) {
6470 return FALSE;
6471 }
6472
6473 if (pf->count < 1) {
6474 return TRUE;
6475 }
6476
6477 or_result = FALSE;
6478
6479 for (i = 0; i < pf->count; i++) {
6480 pfc = &pf->disjunction[i];
6481 and_result = TRUE;
6482 for (j = 0; j < pfc->count; j++) {
6483 pfp = &pfc->conjunction[j];
6484 match = (pfp->text[0] == '\0'
6485 || fc_strcasestr(name, pfp->text));
6486 if (pfp->negate) {
6487 match = !match;
6488 }
6489 and_result = and_result && match;
6490 if (!and_result) {
6491 break;
6492 }
6493 }
6494 or_result = or_result || and_result;
6495 if (or_result) {
6496 break;
6497 }
6498 }
6499
6500 return or_result;
6501 }
6502
6503 /****************************************************************************
6504 Frees all memory used by the property filter.
6505 ****************************************************************************/
property_filter_free(struct property_filter * pf)6506 static void property_filter_free(struct property_filter *pf)
6507 {
6508 struct pf_pattern *pfp;
6509 struct pf_conjunction *pfc;
6510 int i, j;
6511
6512 if (!pf) {
6513 return;
6514 }
6515
6516 for (i = 0; i < pf->count; i++) {
6517 pfc = &pf->disjunction[i];
6518 for (j = 0; j < pfc->count; j++) {
6519 pfp = &pfc->conjunction[j];
6520 if (pfp->text != NULL) {
6521 free(pfp->text);
6522 pfp->text = NULL;
6523 }
6524 }
6525 pfc->count = 0;
6526 }
6527 pf->count = 0;
6528 free(pf);
6529 }
6530
6531 /****************************************************************************
6532 Returns a translated string name for the given "vision layer".
6533 ****************************************************************************/
vision_layer_get_name(enum vision_layer vl)6534 const char *vision_layer_get_name(enum vision_layer vl)
6535 {
6536 switch (vl) {
6537 case V_MAIN:
6538 /* TRANS: Vision layer name. Feel free to leave untranslated. */
6539 return _("Seen (Main)");
6540 case V_INVIS:
6541 /* TRANS: Vision layer name. Feel free to leave untranslated. */
6542 return _("Seen (Invis)");
6543 case V_COUNT:
6544 break;
6545 }
6546
6547 log_error("%s(): Unrecognized vision layer %d.", __FUNCTION__, vl);
6548 return _("Unknown");
6549 }
6550