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