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