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