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