property_grid.vala 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /*
  2. * Copyright (c) 2012-2026 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. namespace Crown
  6. {
  7. public class PropertyGrid : Gtk.Grid
  8. {
  9. GLib.ActionEntry[] actions =
  10. {
  11. { "remove", on_remove, null, null },
  12. { "reset_property", on_reset_property, "s", null },
  13. };
  14. public Expander? _expander;
  15. public Gtk.GestureMultiPress _controller_click;
  16. public GLib.SimpleActionGroup _action_group;
  17. public Database? _db;
  18. public StringId64 _type;
  19. public Guid _id;
  20. public Guid _component_id;
  21. public int _rows;
  22. public double _order;
  23. public bool _visible;
  24. public int label_width_chars;
  25. public Gee.HashMap<string, Gtk.GestureMultiPress> _gestures;
  26. public Gee.HashMap<string, InputField> _widgets;
  27. public Gee.HashMap<InputField, PropertyDefinition?> _definitions;
  28. public void on_remove(GLib.SimpleAction action, GLib.Variant? param)
  29. {
  30. string component_type = _db.type_name(_type);
  31. Guid unit_id = _id;
  32. Unit unit = Unit(_db, unit_id);
  33. Guid component_id;
  34. if (!unit.has_component(out component_id, component_type))
  35. return;
  36. Gee.ArrayList<unowned string> dependents = new Gee.ArrayList<unowned string>();
  37. // Do not remove if any other component needs us.
  38. foreach (var entry in Unit._component_registry.entries) {
  39. Guid dummy;
  40. if (!unit.has_component(out dummy, entry.key))
  41. continue;
  42. string[] component_type_dependencies = ((string)entry.value).split(", ");
  43. if (component_type in component_type_dependencies)
  44. dependents.add(entry.key);
  45. }
  46. if (dependents.size > 0) {
  47. StringBuilder sb = new StringBuilder();
  48. sb.append("Cannot remove %s due to the following dependencies:\n\n".printf(component_type));
  49. foreach (var item in dependents)
  50. sb.append("• %s\n".printf(item));
  51. Gtk.MessageDialog md = new Gtk.MessageDialog(null
  52. , Gtk.DialogFlags.MODAL
  53. , Gtk.MessageType.WARNING
  54. , Gtk.ButtonsType.OK
  55. , sb.str
  56. );
  57. md.set_default_response(Gtk.ResponseType.OK);
  58. md.response.connect(() => { md.destroy(); });
  59. md.show_all();
  60. return;
  61. } else {
  62. unit.remove_component_type(component_type);
  63. }
  64. }
  65. public void on_reset_property(GLib.SimpleAction action, GLib.Variant? param)
  66. {
  67. string property_name = param.get_string();
  68. InputField field = _widgets[property_name];
  69. PropertyDefinition? def = _definitions[field];
  70. field.set_union_value(def.deffault);
  71. }
  72. public void on_expander_button_released(int n_press, double x, double y)
  73. {
  74. if (_controller_click.get_current_button() == Gdk.BUTTON_SECONDARY) {
  75. GLib.Menu menu = new GLib.Menu();
  76. GLib.MenuItem mi;
  77. if (_db != null && _db.object_type(_id) == OBJECT_TYPE_UNIT && _component_id != GUID_ZERO) {
  78. mi = new GLib.MenuItem("Remove Component", null);
  79. mi.set_action_and_target_value("object.remove", null);
  80. menu.append_item(mi);
  81. }
  82. if (menu.get_n_items() > 0) {
  83. Gtk.Popover popover = new Gtk.Popover.from_model(null, menu);
  84. popover.set_relative_to(this);
  85. popover.set_pointing_to({ (int)x, (int)y, 1, 1 });
  86. popover.set_position(Gtk.PositionType.BOTTOM);
  87. popover.popup();
  88. }
  89. }
  90. }
  91. public PropertyGrid(Database? db = null)
  92. {
  93. this.row_spacing = 4;
  94. this.row_homogeneous = true;
  95. this.column_spacing = 12;
  96. this.label_width_chars = 13;
  97. // Data
  98. _expander = null;
  99. _db = db;
  100. _id = GUID_ZERO;
  101. _component_id = GUID_ZERO;
  102. _rows = 0;
  103. _order = 0.0;
  104. _visible = true;
  105. _gestures = new Gee.HashMap<string, Gtk.GestureMultiPress>();
  106. _widgets = new Gee.HashMap<string, InputField>();
  107. _definitions = new Gee.HashMap<InputField, PropertyDefinition?>();
  108. _action_group = new GLib.SimpleActionGroup();
  109. _action_group.add_action_entries(actions, this);
  110. this.insert_action_group("object", _action_group);
  111. }
  112. public PropertyGrid.from_object_type(StringId64 type, Database db)
  113. {
  114. this(db);
  115. _order = db.type_info(type).ui_order;
  116. _type = type;
  117. add_object_type(db.object_definition(type));
  118. }
  119. public PropertyGrid.from_object(Guid id, Database db)
  120. {
  121. this.from_object_type(StringId64(db.object_type(id)), db);
  122. _id = id;
  123. }
  124. public void set_expander(Expander e)
  125. {
  126. assert(_expander == null);
  127. _expander = e;
  128. _controller_click = new Gtk.GestureMultiPress(e);
  129. _controller_click.set_button(0);
  130. _controller_click.released.connect(on_expander_button_released);
  131. }
  132. public Gtk.Widget add_row(string label, Gtk.Widget w, string? tooltip = null)
  133. {
  134. Gtk.Label l = new Gtk.Label(label);
  135. l.width_chars = label_width_chars;
  136. l.xalign = 1.0f;
  137. l.yalign = 0.5f;
  138. l.set_tooltip_text(tooltip);
  139. w.hexpand = true;
  140. this.attach(l, 0, (int)_rows);
  141. this.attach(w, 1, (int)_rows);
  142. ++_rows;
  143. return l;
  144. }
  145. public void add_object_type(PropertyDefinition[] properties)
  146. {
  147. foreach (PropertyDefinition def in properties) {
  148. // Create input field.
  149. InputField? p = null;
  150. switch (def.type) {
  151. case PropertyType.BOOL:
  152. p = new InputBool();
  153. break;
  154. case PropertyType.DOUBLE:
  155. if (def.editor == PropertyEditorType.ANGLE)
  156. p = new InputAngle((double)def.deffault, (double)def.min, (double)def.max);
  157. else
  158. p = new InputDouble((double)def.deffault, (double)def.min, (double)def.max);
  159. break;
  160. case PropertyType.STRING:
  161. if (def.editor == PropertyEditorType.ENUM)
  162. p = new InputEnum((string)def.deffault, def.enum_labels, def.enum_values);
  163. else
  164. p = new InputString();
  165. break;
  166. case PropertyType.VECTOR3:
  167. if (def.editor == PropertyEditorType.COLOR)
  168. p = new InputColor3();
  169. else
  170. p = new InputVector3((Vector3)def.deffault, (Vector3)def.min, (Vector3)def.max);
  171. break;
  172. case PropertyType.QUATERNION:
  173. p = new InputQuaternion();
  174. break;
  175. case PropertyType.RESOURCE:
  176. p = new InputResource(def.resource_type, _db);
  177. break;
  178. case PropertyType.REFERENCE:
  179. p = new InputObject(def.object_type, _db);
  180. break;
  181. case PropertyType.OBJECTS_SET:
  182. continue;
  183. default:
  184. assert(false);
  185. break;
  186. }
  187. p.value_changed.connect(on_property_value_changed);
  188. Gtk.GestureMultiPress click = new Gtk.GestureMultiPress(p);
  189. click.set_propagation_phase(Gtk.PropagationPhase.CAPTURE);
  190. click.set_button(Gdk.BUTTON_SECONDARY);
  191. click.pressed.connect((n_press, x, y) => {
  192. if (click.get_current_button() != Gdk.BUTTON_SECONDARY)
  193. return;
  194. GLib.Menu menu = new GLib.Menu();
  195. GLib.MenuItem mi;
  196. if (_db != null) {
  197. mi = new GLib.MenuItem("Reset to default", null);
  198. mi.set_action_and_target_value("object.reset_property", new GLib.Variant.string(def.name));
  199. menu.append_item(mi);
  200. }
  201. if (menu.get_n_items() > 0) {
  202. Gtk.Popover popover = new Gtk.Popover.from_model(null, menu);
  203. popover.set_relative_to(p);
  204. popover.set_position(Gtk.PositionType.BOTTOM);
  205. popover.popup();
  206. }
  207. });
  208. _gestures[def.name] = click;
  209. _widgets[def.name] = p;
  210. _definitions[p] = def;
  211. if (!def.hidden)
  212. add_row(def.label, p, def.tooltip);
  213. }
  214. }
  215. // Returns true if the property was written.
  216. // The property is written to database only if its value
  217. // differs than the value stored in the database.
  218. public bool write_property_if_changed(PropertyDefinition def, GLib.Value? new_value)
  219. {
  220. bool changed = false;
  221. if (def.type == PropertyType.BOOL) {
  222. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  223. Unit u = Unit(_db, _id);
  224. if (u.get_component_bool(_component_id, def.name, (bool)def.deffault) != new_value) {
  225. u.set_component_bool(_component_id, def.name, (bool)new_value);
  226. changed = true;
  227. }
  228. } else {
  229. if (_db.get_bool(_id, def.name, (bool)def.deffault) != new_value) {
  230. _db.set_bool(_id, def.name, (bool)new_value);
  231. changed = true;
  232. }
  233. }
  234. } else if (def.type == PropertyType.DOUBLE) {
  235. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  236. Unit u = Unit(_db, _id);
  237. if (u.get_component_double(_component_id, def.name, (double)def.deffault) != new_value) {
  238. u.set_component_double(_component_id, def.name, (double)new_value);
  239. changed = true;
  240. }
  241. } else {
  242. if (_db.get_double(_id, def.name, (double)def.deffault) != new_value) {
  243. _db.set_double(_id, def.name, (double)new_value);
  244. changed = true;
  245. }
  246. }
  247. } else if (def.type == PropertyType.STRING) {
  248. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  249. Unit u = Unit(_db, _id);
  250. if (u.get_component_string(_component_id, def.name, (string)def.deffault) != (string)new_value) {
  251. u.set_component_string(_component_id, def.name, (string)new_value);
  252. changed = true;
  253. }
  254. } else {
  255. if (_db.get_string(_id, def.name, (string)def.deffault) != (string)new_value) {
  256. _db.set_string(_id, def.name, (string)new_value);
  257. changed = true;
  258. }
  259. }
  260. } else if (def.type == PropertyType.VECTOR3) {
  261. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  262. Unit u = Unit(_db, _id);
  263. if (Vector3.equal_func(u.get_component_vector3(_component_id, def.name, (Vector3)def.deffault), (Vector3)new_value) == false) {
  264. u.set_component_vector3(_component_id, def.name, (Vector3)new_value);
  265. changed = true;
  266. }
  267. } else {
  268. if (Vector3.equal_func(_db.get_vector3(_id, def.name, (Vector3)def.deffault), (Vector3)new_value) == false) {
  269. _db.set_vector3(_id, def.name, (Vector3)new_value);
  270. changed = true;
  271. }
  272. }
  273. } else if (def.type == PropertyType.QUATERNION) {
  274. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  275. Unit u = Unit(_db, _id);
  276. if (Quaternion.equal_func(u.get_component_quaternion(_component_id, def.name, (Quaternion)def.deffault), (Quaternion)new_value) == false) {
  277. u.set_component_quaternion(_component_id, def.name, (Quaternion)new_value);
  278. changed = true;
  279. }
  280. } else {
  281. if (Quaternion.equal_func(_db.get_quaternion(_id, def.name, (Quaternion)def.deffault), (Quaternion)new_value) == false) {
  282. _db.set_quaternion(_id, def.name, (Quaternion)new_value);
  283. changed = true;
  284. }
  285. }
  286. } else if (def.type == PropertyType.RESOURCE) {
  287. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  288. Unit u = Unit(_db, _id);
  289. if (u.get_component_resource(_component_id, def.name, (string?)def.deffault) != (string?)new_value) {
  290. u.set_component_resource(_component_id, def.name, (string?)new_value);
  291. changed = true;
  292. }
  293. } else {
  294. if (_db.get_resource(_id, def.name, (string?)def.deffault) != (string?)new_value) {
  295. _db.set_resource(_id, def.name, (string?)new_value);
  296. changed = true;
  297. }
  298. }
  299. } else if (def.type == PropertyType.REFERENCE) {
  300. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  301. Unit u = Unit(_db, _id);
  302. if (Guid.equal_func(u.get_component_reference(_component_id, def.name, (Guid)def.deffault), (Guid)new_value) == false) {
  303. u.set_component_reference(_component_id, def.name, (Guid)new_value);
  304. changed = true;
  305. }
  306. } else {
  307. if (Guid.equal_func(_db.get_reference(_id, def.name, (Guid)def.deffault), (Guid)new_value) == false) {
  308. _db.set_reference(_id, def.name, (Guid)new_value);
  309. changed = true;
  310. }
  311. }
  312. } else {
  313. loge("Unknown property type");
  314. }
  315. return changed;
  316. }
  317. public void on_property_value_changed(InputField p, int undo_redo)
  318. {
  319. if (p.is_inconsistent())
  320. return;
  321. if (_id == GUID_ZERO)
  322. return;
  323. PropertyDefinition def = _definitions[p];
  324. Gee.ArrayList<PropertyDefinition?> dynamic_properties = new Gee.ArrayList<PropertyDefinition?>();
  325. Gee.ArrayList<GLib.Value?> dynamic_values = new Gee.ArrayList<GLib.Value?>();
  326. bool changed = false;
  327. save_dynamic_properties_values(ref dynamic_properties, ref dynamic_values);
  328. read_dynamic_properties_ranges_except({ def });
  329. UndoRedo? ur = null;
  330. if (undo_redo == 0)
  331. ur = _db.disable_undo();
  332. changed = restore_dynamic_properties_values_except(dynamic_properties, dynamic_values, { def }) || changed;
  333. changed = write_property_if_changed(def, p.union_value()) || changed;
  334. if (changed)
  335. _db.add_restore_point(ActionType.CHANGE_OBJECTS, new Guid?[] { _id });
  336. if (undo_redo == 0)
  337. _db.restore_undo(ur);
  338. }
  339. public void read_all_properties()
  340. {
  341. foreach (var e in _definitions) {
  342. InputField p = e.key;
  343. PropertyDefinition def = e.value;
  344. p.value_changed.disconnect(on_property_value_changed);
  345. if (def.type == PropertyType.BOOL) {
  346. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  347. Unit u = Unit(_db, _id);
  348. p.set_union_value(u.get_component_bool(_component_id, def.name, (bool)def.deffault));
  349. } else {
  350. p.set_union_value(_db.get_bool(_id, def.name, (bool)def.deffault));
  351. }
  352. } else if (def.type == PropertyType.DOUBLE) {
  353. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  354. Unit u = Unit(_db, _id);
  355. p.set_union_value(u.get_component_double(_component_id, def.name, (double)def.deffault));
  356. } else {
  357. p.set_union_value(_db.get_double(_id, def.name, (double)def.deffault));
  358. }
  359. } else if (def.type == PropertyType.STRING) {
  360. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  361. Unit u = Unit(_db, _id);
  362. p.set_union_value(u.get_component_string(_component_id, def.name, (string)def.deffault));
  363. } else {
  364. p.set_union_value(_db.get_string(_id, def.name, (string)def.deffault));
  365. }
  366. } else if (def.type == PropertyType.VECTOR3) {
  367. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  368. Unit u = Unit(_db, _id);
  369. p.set_union_value(u.get_component_vector3(_component_id, def.name, (Vector3)def.deffault));
  370. } else {
  371. p.set_union_value(_db.get_vector3(_id, def.name, (Vector3)def.deffault));
  372. }
  373. } else if (def.type == PropertyType.QUATERNION) {
  374. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  375. Unit u = Unit(_db, _id);
  376. p.set_union_value(u.get_component_quaternion(_component_id, def.name, (Quaternion)def.deffault));
  377. } else {
  378. p.set_union_value(_db.get_quaternion(_id, def.name, (Quaternion)def.deffault));
  379. }
  380. } else if (def.type == PropertyType.RESOURCE) {
  381. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  382. Unit u = Unit(_db, _id);
  383. p.set_union_value(u.get_component_resource(_component_id, def.name, (string?)def.deffault));
  384. } else {
  385. p.set_union_value(_db.get_resource(_id, def.name, (string?)def.deffault));
  386. }
  387. } else if (def.type == PropertyType.REFERENCE) {
  388. if (_db.object_type(_id) == OBJECT_TYPE_UNIT) {
  389. Unit u = Unit(_db, _id);
  390. p.set_union_value(u.get_component_reference(_component_id, def.name, (Guid)def.deffault));
  391. } else {
  392. p.set_union_value(_db.get_reference(_id, def.name, (Guid)def.deffault));
  393. }
  394. } else {
  395. loge("Unknown property value type");
  396. }
  397. p.value_changed.connect(on_property_value_changed);
  398. }
  399. }
  400. public virtual void read_properties()
  401. {
  402. read_all_properties();
  403. read_dynamic_properties_ranges();
  404. read_all_properties();
  405. }
  406. public void read_dynamic_properties_ranges_except(PropertyDefinition[] excluded)
  407. {
  408. foreach (var e in _definitions) {
  409. PropertyDefinition def = e.value;
  410. int i;
  411. // Skip if excluded.
  412. for (i = 0; i < excluded.length; ++i) {
  413. if (excluded[i].name == def.name)
  414. break;
  415. }
  416. if (i != excluded.length)
  417. continue;
  418. // Read range.
  419. if (def.enum_callback != null) {
  420. InputField p = _widgets[def.name];
  421. InputField parent_p = _widgets[def.enum_property];
  422. p.value_changed.disconnect(on_property_value_changed);
  423. def.enum_callback(parent_p, (InputEnum)p, _db._project);
  424. p.value_changed.connect(on_property_value_changed);
  425. } else if (def.resource_callback != null) {
  426. InputField p = _widgets[def.name];
  427. InputField parent_p = _widgets[def.enum_property];
  428. p.value_changed.disconnect(on_property_value_changed);
  429. def.resource_callback(parent_p, (InputResource)p, _db._project);
  430. p.value_changed.connect(on_property_value_changed);
  431. }
  432. }
  433. }
  434. public void read_dynamic_properties_ranges()
  435. {
  436. read_dynamic_properties_ranges_except({});
  437. }
  438. public void save_dynamic_properties_values(ref Gee.ArrayList<PropertyDefinition?> properties, ref Gee.ArrayList<GLib.Value?> values)
  439. {
  440. foreach (var e in _definitions) {
  441. PropertyDefinition def = e.value;
  442. if (def.enum_callback != null) {
  443. InputField p = _widgets[def.name];
  444. properties.add(def);
  445. values.add(p.union_value());
  446. } else if (def.resource_callback != null) {
  447. InputField p = _widgets[def.name];
  448. properties.add(def);
  449. values.add(p.union_value());
  450. }
  451. }
  452. }
  453. public bool restore_dynamic_properties_values_except(Gee.ArrayList<PropertyDefinition?> properties, Gee.ArrayList<GLib.Value?> values, PropertyDefinition[] excluded)
  454. {
  455. bool changed = false;
  456. for (int i = 0; i < properties.size; ++i) {
  457. PropertyDefinition def = properties[i];
  458. GLib.Value val = values[i];
  459. InputField p = _widgets[def.name];
  460. int j;
  461. // Skip if excluded.
  462. for (j = 0; j < excluded.length; ++j) {
  463. if (excluded[j].name == def.name)
  464. break;
  465. }
  466. if (j != excluded.length)
  467. continue;
  468. // Restore value.
  469. p.value_changed.disconnect(on_property_value_changed);
  470. if (def.enum_callback != null) {
  471. p.set_union_value(val);
  472. if (p.is_inconsistent() || !p.is_inconsistent() && (string)p.union_value() != (string)val)
  473. p.set_union_value(((InputEnum)p).any_valid_id());
  474. } else if (def.resource_callback != null) {
  475. p.set_union_value(val);
  476. }
  477. p.value_changed.connect(on_property_value_changed);
  478. changed = write_property_if_changed(def, p.union_value()) || changed;
  479. }
  480. return changed;
  481. }
  482. public bool restore_dynamic_properties_values(Gee.ArrayList<PropertyDefinition?> properties, Gee.ArrayList<GLib.Value?> values)
  483. {
  484. return restore_dynamic_properties_values_except(properties, values, {});
  485. }
  486. }
  487. public class PropertyGridSet : Gtk.Box
  488. {
  489. public Gtk.ListBox _list_box;
  490. public PropertyGridSet()
  491. {
  492. Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
  493. _list_box = new Gtk.ListBox();
  494. _list_box.selection_mode = Gtk.SelectionMode.NONE;
  495. _list_box.margin_bottom
  496. = this.margin_end
  497. = this.margin_start
  498. = this.margin_top
  499. = 12
  500. ;
  501. _list_box.set_sort_func(sort_function);
  502. _list_box.set_filter_func(filter_function);
  503. this.pack_start(_list_box);
  504. }
  505. public static int sort_function(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2)
  506. {
  507. Expander e1 = (Expander)row1.get_child();
  508. Expander e2 = (Expander)row2.get_child();
  509. double order = ((PropertyGrid)e1._child)._order - ((PropertyGrid)e2._child)._order;
  510. return (int)order;
  511. }
  512. public static bool filter_function(Gtk.ListBoxRow row)
  513. {
  514. Expander e = (Expander)row.get_child();
  515. return ((PropertyGrid)e._child)._visible;
  516. }
  517. public Expander add_property_grid(PropertyGrid cv, string label, string? tooltip = null)
  518. {
  519. Gtk.Label l = new Gtk.Label(null);
  520. l.set_markup("<b>%s</b>".printf(label));
  521. l.xalign = 0.0f;
  522. l.yalign = 0.5f;
  523. l.set_tooltip_text(label);
  524. Expander e = new Expander();
  525. e.custom_header = l;
  526. e.expanded = true;
  527. e.add(cv);
  528. cv.set_expander(e);
  529. Gtk.ListBoxRow row = new Gtk.ListBoxRow();
  530. row.can_focus = false;
  531. row.add(e);
  532. _list_box.add(row);
  533. return e;
  534. }
  535. public Expander add_property_grid_optional(PropertyGrid cv, string label, InputBool InputBool, string? tooltip = null)
  536. {
  537. Gtk.Label l = new Gtk.Label(null);
  538. l.set_markup("<b>%s</b>".printf(label));
  539. l.xalign = 0.0f;
  540. l.yalign = 0.5f;
  541. l.set_tooltip_text(tooltip);
  542. Gtk.Box b = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
  543. b.pack_start(InputBool, false, false);
  544. b.pack_start(l, false, false);
  545. Expander e = new Expander();
  546. e.custom_header = b;
  547. e.expanded = true;
  548. e.add(cv);
  549. cv.set_expander(e);
  550. Gtk.ListBoxRow row = new Gtk.ListBoxRow();
  551. row.can_focus = false;
  552. row.add(e);
  553. _list_box.add(row);
  554. return e;
  555. }
  556. }
  557. } /* namespace Crown */