OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4ConsoleQtPropListViewer.cpp
Go to the documentation of this file.
1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2013, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 
17 #include "C4Include.h"
18 #include "script/C4Value.h"
22 #include "editor/C4Console.h"
23 #include "object/C4Object.h"
24 #include "object/C4GameObjects.h"
25 #include "object/C4DefList.h"
26 #include "object/C4Def.h"
27 #include "script/C4Effect.h"
28 #include "script/C4AulExec.h"
30 
31 
32 /* Property path for property setting synchronization */
33 
34 C4PropertyPath::C4PropertyPath(C4PropList *target) : get_path_type(PPT_Root), set_path_type(PPT_Root)
35 {
36  // Build string to set target
37  if (target)
38  {
39  // Object target
40  C4Object *obj = target->GetObject();
41  C4PropListStatic *target_static;
42  if (obj)
43  {
44  get_path.Format("Object(%d)", (int)obj->Number);
45  root = get_path;
46  }
47  else if ((target_static = target->IsStatic()))
48  {
49  // Global static prop lists: Resolve name
50  get_path = target_static->GetDataString();
51  root = get_path;
52  }
53  else
54  {
55  // Otherwise leave empty. We do not want assignments into temporary values, etc.
56  }
57  }
58 }
59 
60 C4PropertyPath::C4PropertyPath(C4Effect *fx, C4Object *target_obj) : get_path_type(PPT_Root), set_path_type(PPT_Root)
61 {
62  // Effect property path: Represent as GetEffect("name", Object(%d), index) for object effects and GetEffect("name", nil, index) for global effects
63  if (!fx) return;
64  const char *name = fx->GetName();
65  int32_t index = 0;
66  for (C4Effect *ofx = target_obj ? target_obj->pEffects : ::ScriptEngine.pGlobalEffects; ofx; ofx = ofx->pNext)
67  if (ofx == fx) break; else if (!strcmp(ofx->GetName(), name)) ++index;
68  if (target_obj)
69  {
70  get_path.Format("GetEffect(\"%s\", Object(%d), %d)", name, (int)target_obj->Number, (int)index);
71  root.Format("Object(%d)", (int)target_obj->Number);
72  }
73  else
74  {
75  get_path.Format("GetEffect(\"%s\", nil, %d)", name, (int)index);
76  root = ::Strings.P[P_Global].GetData();
77  }
78 }
79 
80 C4PropertyPath::C4PropertyPath(const C4PropertyPath &parent, int32_t elem_index) : root(parent.root)
81 {
82  get_path.Format("%s[%d]", parent.GetGetPath(), (int)elem_index);
83  get_path_type = set_path_type = PPT_Index;
84 }
85 
86 C4PropertyPath::C4PropertyPath(const C4PropertyPath &parent, const char *child_property)
87  : get_path_type(PPT_Property), set_path_type(PPT_Property), root(parent.root)
88 {
89  get_path.Format("%s.%s", parent.GetGetPath(), child_property);
90 }
91 
92 void C4PropertyPath::SetSetPath(const C4PropertyPath &parent, const char *child_property, C4PropertyPath::PathType path_type)
93 {
94  set_path_type = path_type;
95  if (path_type == PPT_Property)
96  set_path.Format("%s.%s", parent.GetGetPath(), child_property);
97  else if (path_type == PPT_SetFunction)
98  set_path.Format("%s->%s", parent.GetGetPath(), child_property);
99  else if (path_type == PPT_GlobalSetFunction)
100  {
101  set_path.Copy(parent.GetGetPath());
102  argument.Copy(child_property);
103  }
104  else if (path_type == PPT_RootSetFunction)
105  {
106  set_path.Format("%s->%s", parent.GetRoot(), child_property);
107  }
108  else
109  {
110  assert(false);
111  }
112 }
113 
114 void C4PropertyPath::SetProperty(const char *set_string) const
115 {
116  // Compose script to update property
117  const char *set_path_c = GetSetPath();
118  StdStrBuf script;
119  if (set_path_type == PPT_SetFunction || set_path_type == PPT_RootSetFunction)
120  script.Format("%s(%s)", set_path_c, set_string);
121  else if (set_path_type == PPT_GlobalSetFunction)
122  script.Format("%s(%s,%s)", argument.getData(), set_path_c, set_string);
123  else
124  script.Format("%s=%s", set_path_c, set_string);
125  // Execute synced scripted
127 }
128 
129 void C4PropertyPath::SetProperty(const C4Value &to_val, const C4PropListStatic *ignore_reference_parent) const
130 {
131  SetProperty(to_val.GetDataString(9999999, ignore_reference_parent).getData());
132 }
133 
134 C4Value C4PropertyPath::ResolveValue() const
135 {
136  if (!get_path.getLength()) return C4VNull;
137  return AulExec.DirectExec(::ScriptEngine.GetPropList(), get_path.getData(), "resolve property", false, nullptr);
138 }
139 
140 C4Value C4PropertyPath::ResolveRoot() const
141 {
142  if (!root.getLength()) return C4VNull;
143  return AulExec.DirectExec(::ScriptEngine.GetPropList(), root.getData(), "resolve property root", false, nullptr);
144 }
145 
146 void C4PropertyPath::DoCall(const char *call_string) const
147 {
148  // Compose script call
149  StdStrBuf script;
150  script.Format(call_string, get_path.getData());
151  // Execute synced scripted
153 }
154 
155 
156 /* Property delegate base class */
157 
158 C4PropertyDelegate::C4PropertyDelegate(const C4PropertyDelegateFactory *factory, C4PropList *props)
159  : QObject(), factory(factory), set_function_type(C4PropertyPath::PPT_SetFunction)
160 {
161  // Resolve getter+setter callback names
162  if (props)
163  {
164  creation_props = C4VPropList(props);
165  name = props->GetPropertyStr(P_Name);
166  set_function = props->GetPropertyStr(P_Set);
167  if (props->GetPropertyBool(P_SetGlobal))
168  {
169  set_function_type = C4PropertyPath::PPT_GlobalSetFunction;
170  }
171  else if (props->GetPropertyBool(P_SetRoot))
172  {
173  set_function_type = C4PropertyPath::PPT_RootSetFunction;
174  }
175  else
176  {
177  set_function_type = C4PropertyPath::PPT_SetFunction;
178  }
179  async_get_function = props->GetPropertyStr(P_AsyncGet);
180  update_callback = props->GetPropertyStr(P_OnUpdate);
181  }
182 }
183 
184 void C4PropertyDelegate::UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const
185 {
186  editor->setGeometry(option.rect);
187 }
188 
189 bool C4PropertyDelegate::GetPropertyValueBase(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
190 {
191  switch (container.GetType())
192  {
193  case C4V_PropList:
194  return container._getPropList()->GetPropertyByS(key, out_val);
195  case C4V_Array:
196  *out_val = container._getArray()->GetItem(index);
197  return true;
198  default:
199  return false;
200  }
201 }
202 
203 bool C4PropertyDelegate::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
204 {
205  if (async_get_function)
206  {
207  C4PropList *props = container.getPropList();
208  if (props)
209  {
210  *out_val = props->Call(async_get_function.Get());
211  return true;
212  }
213  return false;
214  }
215  else
216  {
217  return GetPropertyValueBase(container, key, index, out_val);
218  }
219 }
220 
221 QString C4PropertyDelegate::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
222 {
223  return QString(v.GetDataString().getData());
224 }
225 
226 QColor C4PropertyDelegate::GetDisplayTextColor(const C4Value &val, class C4Object *obj) const
227 {
228  return QColor(); // invalid = default
229 }
230 
231 QColor C4PropertyDelegate::GetDisplayBackgroundColor(const C4Value &val, class C4Object *obj) const
232 {
233  return QColor(); // invalid = default
234 }
235 
236 C4PropertyPath C4PropertyDelegate::GetPathForProperty(C4ConsoleQtPropListModelProperty *editor_prop) const
237 {
238  C4PropertyPath path;
239  if (editor_prop->property_path.IsEmpty())
240  path = C4PropertyPath(editor_prop->parent_value.getPropList());
241  else
242  path = editor_prop->property_path;
243  return GetPathForProperty(path, editor_prop->key ? editor_prop->key->GetCStr() : nullptr);
244 }
245 
246 C4PropertyPath C4PropertyDelegate::GetPathForProperty(const C4PropertyPath &parent_path, const char *default_subpath) const
247 {
248  // Get path
249  C4PropertyPath subpath;
250  if (default_subpath && *default_subpath)
251  subpath = C4PropertyPath(parent_path, default_subpath);
252  else
253  subpath = parent_path;
254  // Set path
255  if (GetSetFunction())
256  {
257  subpath.SetSetPath(parent_path, GetSetFunction(), set_function_type);
258  }
259  return subpath;
260 }
261 
262 
263 /* Integer delegate */
264 
265 C4PropertyDelegateInt::C4PropertyDelegateInt(const C4PropertyDelegateFactory *factory, C4PropList *props)
266  : C4PropertyDelegate(factory, props), min(std::numeric_limits<int32_t>::min()), max(std::numeric_limits<int32_t>::max()), step(1)
267 {
268  if (props)
269  {
270  min = props->GetPropertyInt(P_Min, min);
271  max = props->GetPropertyInt(P_Max, max);
272  step = props->GetPropertyInt(P_Step, step);
273  }
274 }
275 
276 void C4PropertyDelegateInt::SetEditorData(QWidget *editor, const C4Value &val, const C4PropertyPath &property_path) const
277 {
278  QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
279  spinBox->setValue(val.getInt());
280 }
281 
282 void C4PropertyDelegateInt::SetModelData(QObject *editor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
283 {
284  QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
285  spinBox->interpretText();
286  property_path.SetProperty(C4VInt(spinBox->value()));
287  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
288 }
289 
290 QWidget *C4PropertyDelegateInt::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
291 {
292  QSpinBox *editor = new QSpinBox(parent);
293  editor->setMinimum(min);
294  editor->setMaximum(max);
295  editor->setSingleStep(step);
296  connect(editor, &QSpinBox::editingFinished, this, [editor, this]() {
297  emit EditingDoneSignal(editor);
298  });
299  // Selection in child enum: Direct focus
300  if (by_selection && is_child) editor->setFocus();
301  return editor;
302 }
303 
304 bool C4PropertyDelegateInt::IsPasteValid(const C4Value &val) const
305 {
306  // Check int type and limits
307  if (val.GetType() != C4V_Int) return false;
308  int32_t ival = val._getInt();
309  return (ival >= min && ival <= max);
310 }
311 
312 
313 /* String delegate */
314 
315 C4PropertyDelegateString::C4PropertyDelegateString(const C4PropertyDelegateFactory *factory, C4PropList *props)
316  : C4PropertyDelegate(factory, props)
317 {
318 }
319 
320 void C4PropertyDelegateString::SetEditorData(QWidget *editor, const C4Value &val, const C4PropertyPath &property_path) const
321 {
322  Editor *line_edit = static_cast<Editor*>(editor);
323  C4String *s = val.getStr();
324  line_edit->setText(QString(s ? s->GetCStr() : ""));
325 }
326 
327 void C4PropertyDelegateString::SetModelData(QObject *editor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
328 {
329  Editor *line_edit = static_cast<Editor*>(editor);
330  // Only set model data when pressing Enter explicitely; not just when leaving
331  if (line_edit->commit_pending)
332  {
333  QString new_value = line_edit->text();
334  // TODO: Would be better to handle escaping in the C4Value-to-string code
335  new_value = new_value.replace("\\", "\\\\").replace("\"", "\\\"");
336  property_path.SetProperty(C4VString(new_value.toUtf8()));
337  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
338  line_edit->commit_pending = false;
339  }
340 }
341 
342 QWidget *C4PropertyDelegateString::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
343 {
344  Editor *editor = new Editor(parent);
345  // EditingDone on return or when leaving edit field after a change has been made
346  connect(editor, &QLineEdit::returnPressed, editor, [this, editor]() {
347  editor->commit_pending = true;
348  emit EditingDoneSignal(editor);
349  });
350  connect(editor, &QLineEdit::textEdited, this, [editor, this]() {
351  editor->commit_pending = true;
352  });
353  // Selection in child enum: Direct focus
354  if (by_selection && is_child) editor->setFocus();
355  return editor;
356 }
357 
358 QString C4PropertyDelegateString::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
359 {
360  // Raw string without ""
361  C4String *s = v.getStr();
362  return QString(s ? s->GetCStr() : "");
363 }
364 
365 bool C4PropertyDelegateString::IsPasteValid(const C4Value &val) const
366 {
367  // Check string type
368  if (val.GetType() != C4V_String) return false;
369  return true;
370 }
371 
372 
373 /* Delegate editor: Text left and button right */
374 
375 C4PropertyDelegateLabelAndButtonWidget::C4PropertyDelegateLabelAndButtonWidget(QWidget *parent)
376  : QWidget(parent), layout(nullptr), label(nullptr), button(nullptr), button_pending(false)
377 {
378  layout = new QHBoxLayout(this);
379  layout->setContentsMargins(0, 0, 0, 0);
380  layout->setMargin(0);
381  layout->setSpacing(0);
382  label = new QLabel(this);
383  QPalette palette = label->palette();
384  palette.setColor(label->foregroundRole(), palette.color(QPalette::HighlightedText));
385  palette.setColor(label->backgroundRole(), palette.color(QPalette::Highlight));
386  label->setPalette(palette);
387  layout->addWidget(label);
388  button = new QPushButton(QString(LoadResStr("IDS_CNS_MORE")), this);
389  layout->addWidget(button);
390  // Make sure to draw over view in background
391  setPalette(palette);
392  setAutoFillBackground(true);
393 }
394 
395 
396 /* Descend path delegate base class for arrays and proplist */
397 
398 C4PropertyDelegateDescendPath::C4PropertyDelegateDescendPath(const class C4PropertyDelegateFactory *factory, C4PropList *props)
399  : C4PropertyDelegate(factory, props), edit_on_selection(true)
400 {
401  if (props)
402  {
403  info_proplist = C4VPropList(props); // Descend info is this definition
404  edit_on_selection = props->GetPropertyBool(P_EditOnSelection, edit_on_selection);
405  descend_path = props->GetPropertyStr(P_DescendPath);
406  }
407 }
408 
409 void C4PropertyDelegateDescendPath::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
410 {
411  Editor *editor = static_cast<Editor *>(aeditor);
412  editor->label->setText(GetDisplayString(val, nullptr, false));
413  editor->last_value = val;
414  editor->property_path = property_path;
415  if (editor->button_pending) emit editor->button->pressed();
416 }
417 
418 QWidget *C4PropertyDelegateDescendPath::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
419 {
420  // Otherwise create display and button to descend path
421  Editor *editor;
422  std::unique_ptr<Editor> peditor((editor = new Editor(parent)));
423  connect(editor->button, &QPushButton::pressed, this, [editor, this]() {
424  // Value to descend into: Use last value on auto-press because it will not have been updated into the game yet
425  // (and cannot be without going async in network mode)
426  // On regular button press, re-resolve path to value
427  C4Value val = editor->button_pending ? editor->last_value : editor->property_path.ResolveValue();
428  bool is_proplist = !!val.getPropList(), is_array = !!val.getArray();
429  if (is_proplist || is_array)
430  {
431  C4PropList *info_proplist = this->info_proplist.getPropList();
432  // Allow descending into a sub-path
433  C4PropertyPath descend_property_path(editor->property_path);
434  if (is_proplist && descend_path)
435  {
436  // Descend value into sub-path
437  val._getPropList()->GetPropertyByS(descend_path.Get(), &val);
438  // Descend info_proplist into sub-path
439  if (info_proplist)
440  {
441  C4PropList *info_editorprops = info_proplist->GetPropertyPropList(P_EditorProps);
442  if (info_editorprops)
443  {
444  C4Value sub_info_proplist_val;
445  info_editorprops->GetPropertyByS(descend_path.Get(), &sub_info_proplist_val);
446  info_proplist = sub_info_proplist_val.getPropList();
447  }
448  }
449  // Descend property path into sub-path
450  descend_property_path = C4PropertyPath(descend_property_path, descend_path->GetCStr());
451  }
452  // No info proplist: Fall back to regular proplist viewing mode
453  if (!info_proplist) info_proplist = val.getPropList();
454  this->factory->GetPropertyModel()->DescendPath(val, info_proplist, descend_property_path);
455  ::Console.EditCursor.InvalidateSelection();
456  }
457  });
458  if (by_selection && edit_on_selection) editor->button_pending = true;
459  return peditor.release();
460 }
461 
462 
463 /* Array descend delegate */
464 
465 C4PropertyDelegateArray::C4PropertyDelegateArray(const class C4PropertyDelegateFactory *factory, C4PropList *props)
466  : C4PropertyDelegateDescendPath(factory, props), max_array_display(0), element_delegate(nullptr)
467 {
468  if (props)
469  {
470  max_array_display = props->GetPropertyInt(P_Display);
471  }
472 }
473 
474 void C4PropertyDelegateArray::ResolveElementDelegate() const
475 {
476  if (!element_delegate)
477  {
478  C4Value element_delegate_value;
479  C4PropList *info_proplist = this->info_proplist.getPropList();
480  if (info_proplist) info_proplist->GetProperty(P_Elements, &element_delegate_value);
481  element_delegate = factory->GetDelegateByValue(element_delegate_value);
482  }
483 }
484 
485 QString C4PropertyDelegateArray::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
486 {
487  C4ValueArray *arr = v.getArray();
488  if (!arr) return QString(LoadResStr("IDS_CNS_INVALID"));
489  int32_t n = v._getArray()->GetSize();
490  ResolveElementDelegate();
491  if (max_array_display && n)
492  {
493  QString result = "[";
494  for (int32_t i = 0; i < std::min<int32_t>(n, max_array_display); ++i)
495  {
496  if (i) result += ",";
497  result += element_delegate->GetDisplayString(v._getArray()->GetItem(i), obj, true);
498  }
499  if (n > max_array_display) result += ",...";
500  result += "]";
501  return result;
502  }
503  else if (n || !short_names)
504  {
505  // Default display (or display with 0 elements): Just show element number
506  return QString(LoadResStr("IDS_CNS_ARRAYSHORT")).arg(n);
507  }
508  else
509  {
510  // Short display of empty array: Just leave it out.
511  return QString("");
512  }
513 }
514 
515 bool C4PropertyDelegateArray::IsPasteValid(const C4Value &val) const
516 {
517  // Check array type and all contents
518  C4ValueArray *arr = val.getArray();
519  if (!arr) return false;
520  int32_t n = arr->GetSize();
521  if (n)
522  {
523  ResolveElementDelegate();
524  for (int32_t i = 0; i < arr->GetSize(); ++i)
525  {
526  C4Value item = arr->GetItem(i);
527  if (!element_delegate->IsPasteValid(item)) return false;
528  }
529  }
530  return true;
531 }
532 
533 
534 /* Proplist descend delegate */
535 
536 C4PropertyDelegatePropList::C4PropertyDelegatePropList(const class C4PropertyDelegateFactory *factory, C4PropList *props)
537  : C4PropertyDelegateDescendPath(factory, props)
538 {
539  if (props)
540  {
541  display_string = props->GetPropertyStr(P_Display);
542  }
543 }
544 
545 QString C4PropertyDelegatePropList::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
546 {
547  C4PropList *data = v.getPropList();
548  if (!data) return QString(LoadResStr("IDS_CNS_INVALID"));
549  if (!display_string) return QString("{...}");
550  C4PropList *info_proplist = this->info_proplist.getPropList();
551  C4PropList *info_editorprops = info_proplist ? info_proplist->GetPropertyPropList(P_EditorProps) : nullptr;
552  // Replace all {{name}} by property values of name
553  QString result = display_string->GetCStr();
554  int32_t pos0, pos1;
555  C4Value cv;
556  while ((pos0 = result.indexOf("{{")) >= 0)
557  {
558  pos1 = result.indexOf("}}", pos0+2);
559  if (pos1 < 0) break; // placeholder not closed
560  // Get child value
561  QString substring = result.mid(pos0+2, pos1-pos0-2);
562  C4RefCntPointer<C4String> psubstring = ::Strings.RegString(substring.toUtf8());
563  if (!data->GetPropertyByS(psubstring.Get(), &cv)) cv.Set0();
564  // Try to display using child delegate
565  QString display_value;
566  if (info_editorprops)
567  {
568  C4Value child_delegate_val;
569  if (info_editorprops->GetPropertyByS(psubstring.Get(), &child_delegate_val))
570  {
571  C4PropertyDelegate *child_delegate = factory->GetDelegateByValue(child_delegate_val);
572  if (child_delegate)
573  {
574  display_value = child_delegate->GetDisplayString(cv, obj, true);
575  }
576  }
577  }
578  // If there is no child delegate, fall back to GetDataString()
579  if (display_value.isEmpty()) display_value = cv.GetDataString().getData();
580  // Put value into display string
581  result.replace(pos0, pos1 - pos0 + 2, display_value);
582  }
583  return result;
584 }
585 
586 bool C4PropertyDelegatePropList::IsPasteValid(const C4Value &val) const
587 {
588  // Check proplist type
589  C4PropList *pval = val.getPropList();
590  if (!pval) return false;
591  // Are there restrictions on allowed properties?
592  C4PropList *info_proplist = this->info_proplist.getPropList();
593  C4PropList *info_editorprops = info_proplist ? info_proplist->GetPropertyPropList(P_EditorProps) : nullptr;
594  if (!info_editorprops) return true; // No restrictions: Allow everything
595  // Otherwise all types properties must be valid for paste
596  // (Extra properties are OK)
597  std::vector< C4String * > properties = info_editorprops->GetUnsortedProperties(nullptr, nullptr);
598  for (C4String *prop_name : properties)
599  {
600  if (prop_name == &::Strings.P[P_Prototype]) continue;
601  C4Value child_delegate_val;
602  if (!info_editorprops->GetPropertyByS(prop_name, &child_delegate_val)) continue;
603  C4PropertyDelegate *child_delegate = factory->GetDelegateByValue(child_delegate_val);
604  if (!child_delegate) continue;
605  C4Value child_val;
606  pval->GetPropertyByS(prop_name, &child_val);
607  if (!child_delegate->IsPasteValid(child_val)) return false;
608  }
609  return true;
610 }
611 
612 
613 /* Effect delegate: Allows removal and descend into proplist */
614 
615 C4PropertyDelegateEffectEditor::C4PropertyDelegateEffectEditor(QWidget *parent) : QWidget(parent), layout(nullptr), remove_button(nullptr), edit_button(nullptr)
616 {
617  layout = new QHBoxLayout(this);
618  layout->setContentsMargins(0, 0, 0, 0);
619  layout->setMargin(0);
620  layout->setSpacing(0);
621  remove_button = new QPushButton(QString(LoadResStr("IDS_CNS_REMOVE")), this);
622  layout->addWidget(remove_button);
623  edit_button = new QPushButton(QString(LoadResStr("IDS_CNS_MORE")), this);
624  layout->addWidget(edit_button);
625  // Make sure to draw over view in background
626  setAutoFillBackground(true);
627 }
628 
629 C4PropertyDelegateEffect::C4PropertyDelegateEffect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
630  : C4PropertyDelegate(factory, props)
631 {
632 }
633 
634 void C4PropertyDelegateEffect::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
635 {
636  Editor *editor = static_cast<Editor *>(aeditor);
637  editor->property_path = property_path;
638 }
639 
640 QWidget *C4PropertyDelegateEffect::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
641 {
642  Editor *editor;
643  std::unique_ptr<Editor> peditor((editor = new Editor(parent)));
644  // Remove effect button
645  connect(editor->remove_button, &QPushButton::pressed, this, [editor, this]() {
646  // Compose an effect remove call
647  editor->property_path.DoCall("RemoveEffect(nil, nil, %s)");
648  emit EditingDoneSignal(editor);
649  });
650  // Edit effect button
651  connect(editor->edit_button, &QPushButton::pressed, this, [editor, this]() {
652  // Descend into effect proplist (if the effect still exists)
653  C4Value effect_val = editor->property_path.ResolveValue();
654  C4PropList *effect_proplist = effect_val.getPropList();
655  if (!effect_proplist)
656  {
657  // Effect lost
658  emit EditingDoneSignal(editor);
659  }
660  else
661  {
662  // Effect OK. Edit it.
663  this->factory->GetPropertyModel()->DescendPath(effect_val, effect_proplist, editor->property_path);
664  ::Console.EditCursor.InvalidateSelection();
665  }
666  });
667  return peditor.release();
668 }
669 
670 QString C4PropertyDelegateEffect::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
671 {
672  C4PropList *effect_proplist = v.getPropList();
673  C4Effect *effect = effect_proplist ? effect_proplist->GetEffect() : nullptr;
674  if (effect)
675  {
676  if (effect->IsActive())
677  {
678  return QString("t=%1, interval=%2").arg(effect->iTime).arg(effect->iInterval);
679  }
680  else
681  {
682  return QString(LoadResStr("IDS_CNS_DEADEFFECT"));
683  }
684  }
685  else
686  {
687  return QString("nil");
688  }
689 }
690 
691 bool C4PropertyDelegateEffect::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
692 {
693  // Resolve effect by calling script function
694  if (!key) return false;
695  *out_val = AulExec.DirectExec(::ScriptEngine.GetPropList(), key->GetCStr(), "resolve effect", false, nullptr);
696  return true;
697 }
698 
699 C4PropertyPath C4PropertyDelegateEffect::GetPathForProperty(C4ConsoleQtPropListModelProperty *editor_prop) const
700 {
701  // Property path is used directly for getting effect. No set function needed.
702  return editor_prop->property_path;
703 }
704 
705 
706 /* Color delegate */
707 
708 C4PropertyDelegateColor::C4PropertyDelegateColor(const class C4PropertyDelegateFactory *factory, C4PropList *props)
709  : C4PropertyDelegate(factory, props), alpha_mask(0u)
710 {
711  if (props)
712  {
713  alpha_mask = props->GetPropertyInt(P_Alpha) << 24;
714  }
715 }
716 
717 uint32_t GetTextColorForBackground(uint32_t background_color)
718 {
719  // White text on dark background; black text on bright background
720  uint8_t r = (background_color >> 16) & 0xff;
721  uint8_t g = (background_color >> 8) & 0xff;
722  uint8_t b = (background_color >> 0) & 0xff;
723  int32_t lgt = r * 30 + g * 59 + b * 11;
724  return (lgt > 16000) ? 0 : 0xffffff;
725 }
726 
727 void C4PropertyDelegateColor::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
728 {
729  Editor *editor = static_cast<Editor *>(aeditor);
730  uint32_t background_color = static_cast<uint32_t>(val.getInt()) & 0xffffff;
731  uint32_t foreground_color = GetTextColorForBackground(background_color);
732  QPalette palette = editor->label->palette();
733  palette.setColor(editor->label->backgroundRole(), QColor(QRgb(background_color)));
734  palette.setColor(editor->label->foregroundRole(), QColor(QRgb(foreground_color)));
735  editor->label->setPalette(palette);
736  editor->label->setAutoFillBackground(true);
737  editor->label->setText(GetDisplayString(val, nullptr, false));
738  editor->last_value = val;
739 }
740 
741 void C4PropertyDelegateColor::SetModelData(QObject *aeditor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
742 {
743  Editor *editor = static_cast<Editor *>(aeditor);
744  property_path.SetProperty(editor->last_value);
745  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
746 }
747 
748 QWidget *C4PropertyDelegateColor::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
749 {
750  Editor *editor;
751  std::unique_ptr<Editor> peditor((editor = new Editor(parent)));
752  connect(editor->button, &QPushButton::pressed, this, [editor, this]() {
753  this->OpenColorDialogue(editor);
754  });
755  // Selection in child enum: Open dialogue immediately
756  if (by_selection && is_child) OpenColorDialogue(editor);
757  return peditor.release();
758 }
759 
760 QString C4PropertyDelegateColor::GetDisplayString(const C4Value &v, C4Object *obj, bool short_names) const
761 {
762  return QString("#%1").arg(uint32_t(v.getInt()), 8, 16, QChar('0'));
763 }
764 
765 QColor C4PropertyDelegateColor::GetDisplayTextColor(const C4Value &val, class C4Object *obj) const
766 {
767  uint32_t background_color = static_cast<uint32_t>(val.getInt()) & 0xffffff;
768  uint32_t foreground_color = GetTextColorForBackground(background_color);
769  return QColor(foreground_color);
770 }
771 
772 QColor C4PropertyDelegateColor::GetDisplayBackgroundColor(const C4Value &val, class C4Object *obj) const
773 {
774  return static_cast<uint32_t>(val.getInt()) & 0xffffff;
775 }
776 
777 bool C4PropertyDelegateColor::IsPasteValid(const C4Value &val) const
778 {
779  // Color is always int
780  if (val.GetType() != C4V_Int) return false;
781  return true;
782 }
783 
784 void C4PropertyDelegateColor::OpenColorDialogue(C4PropertyDelegateLabelAndButtonWidget *editor) const
785 {
786  // Show actual dialogue to change the color
787  QColor clr = QColorDialog::getColor(QColor(editor->last_value.getInt() & (~alpha_mask)), editor, QString(), QColorDialog::ShowAlphaChannel);
788  editor->last_value.SetInt(clr.rgba() | alpha_mask);
789  this->SetEditorData(editor, editor->last_value, C4PropertyPath()); // force update on display
790  emit EditingDoneSignal(editor);
791 }
792 
793 
794 /* Enum delegate combo box item delegate */
795 
796 bool C4StyledItemDelegateWithButton::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
797 {
798  // Mouse move over a cell: Display tooltip if over help button
799  QEvent::Type trigger_type = (button_type == BT_Help) ? QEvent::MouseMove : QEvent::MouseButtonPress;
800  if (event->type() == trigger_type)
801  {
802  QVariant btn = model->data(index, Qt::DecorationRole);
803  if (!btn.isNull())
804  {
805  QMouseEvent *mevent = static_cast<QMouseEvent *>(event);
806  if (option.rect.contains(mevent->localPos().toPoint()))
807  {
808  if (mevent->localPos().x() >= option.rect.x() + option.rect.width() - option.rect.height())
809  {
810  switch (button_type)
811  {
812  case BT_Help:
814  {
815  QString tooltip_text = model->data(index, Qt::ToolTipRole).toString();
816  QToolTip::showText(mevent->globalPos(), tooltip_text);
817  }
818  break;
819  case BT_PlaySound:
820  StartSoundEffect(model->data(index, Qt::ToolTipRole).toString().toUtf8());
821  return true; // handled
822  }
823  }
824  }
825  }
826  }
827  return QStyledItemDelegate::editorEvent(event, model, option, index);
828 }
829 
830 void C4StyledItemDelegateWithButton::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
831 {
832  // Paint icon on the right
833  QStyleOptionViewItem override_option = option;
834  override_option.decorationPosition = QStyleOptionViewItem::Right;
835  QStyledItemDelegate::paint(painter, override_option, index);
836 }
837 
838 
839 
840 /* Enum delegate combo box */
841 
842 C4DeepQComboBox::C4DeepQComboBox(QWidget *parent, C4StyledItemDelegateWithButton::ButtonType button_type, bool editable)
843  : QComboBox(parent), last_popup_height(0), is_next_close_blocked(false), editable(editable), manual_text_edited(false)
844 {
845  item_delegate.reset(new C4StyledItemDelegateWithButton(button_type));
846  QTreeView *view = new QTreeView(this);
847  view->setFrameShape(QFrame::NoFrame);
848  view->setSelectionBehavior(QTreeView::SelectRows);
849  view->setAllColumnsShowFocus(true);
850  view->header()->hide();
851  view->setItemDelegate(item_delegate.get());
852  setEditable(editable);
853  // On expansion, enlarge view if necessery
854  connect(view, &QTreeView::expanded, this, [this, view](const QModelIndex &index)
855  {
856  if (this->model() && view->parentWidget())
857  {
858  int child_row_count = this->model()->rowCount(index);
859  if (child_row_count > 0)
860  {
861  // Get space to contain expanded leaf+1 item
862  QModelIndex last_index = this->model()->index(child_row_count - 1, 0, index);
863  int needed_height = view->visualRect(last_index).bottom() - view->visualRect(index).top() + view->height() - view->parentWidget()->height() + view->visualRect(last_index).height();
864  int available_height = QApplication::desktop()->availableGeometry(view->mapToGlobal(QPoint(1, 1))).height(); // but do not expand past screen size
865  int new_height = std::min(needed_height, available_height - 20);
866  if (view->parentWidget()->height() < new_height) view->parentWidget()->resize(view->parentWidget()->width(), (this->last_popup_height=new_height));
867  }
868  }
869  });
870  // On selection, highlight object in editor
871  view->setMouseTracking(true);
872  connect(view, &QTreeView::entered, this, [this](const QModelIndex &index)
873  {
874  C4Object *obj = nullptr;
875  int32_t obj_number = this->model()->data(index, ObjectHighlightRole).toInt();
876  if (obj_number) obj = ::Objects.SafeObjectPointer(obj_number);
878  });
879  // New item selection through combo box: Update model position
880  connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
881  [this](int index)
882  {
883  QModelIndex current = this->view()->currentIndex();
884  QVariant selected_data = this->model()->data(current, OptionIndexRole);
885  if (selected_data.type() == QVariant::Int)
886  {
887  // Reset manual text edit flag because the text is now provided by the view
888  manual_text_edited = false;
889  // Finish selection
890  setCurrentModelIndex(current);
891  emit NewItemSelected(selected_data.toInt());
892  }
893  });
894  // New text typed in
895  if (editable)
896  {
897  // text change event only sent after manual text change
898  connect(lineEdit(), &QLineEdit::textEdited, [this](const QString &text)
899  {
900  manual_text_edited = true;
901  last_edited_text = text;
902  });
903  // reflect in data after return press and when focus is lost
904  connect(lineEdit(), &QLineEdit::returnPressed, [this]()
905  {
906  if (manual_text_edited)
907  {
908  emit TextChanged(last_edited_text);
909  manual_text_edited = false;
910  }
911  });
912  connect(lineEdit(), &QLineEdit::editingFinished, [this]()
913  {
914  if (manual_text_edited)
915  {
916  emit TextChanged(last_edited_text);
917  manual_text_edited = false;
918  }
919  });
920  }
921  // Connect view to combobox
922  setView(view);
923  view->viewport()->installEventFilter(this);
924  // No help icons in main box, unless dropped down
925  default_icon_size = iconSize();
926  setIconSize(QSize(0, 0));
927 }
928 
929 void C4DeepQComboBox::showPopup()
930 {
931  // New selection: Reset to root of model
932  setRootModelIndex(QModelIndex());
933  setIconSize(default_icon_size);
934  QComboBox::showPopup();
935  view()->setMinimumWidth(200); // prevent element list from becoming too small in nested dialogues
936  if (last_popup_height && view()->parentWidget()) view()->parentWidget()->resize(view()->parentWidget()->width(), last_popup_height);
937 }
938 
939 void C4DeepQComboBox::hidePopup()
940 {
941  // Cleanup tree combobox
943  setIconSize(QSize(0, 0));
944  QComboBox::hidePopup();
945 }
946 
947 void C4DeepQComboBox::setCurrentModelIndex(QModelIndex new_index)
948 {
949  setRootModelIndex(new_index.parent());
950  setCurrentIndex(new_index.row());
951  // Adjust text
952  if (editable)
953  {
954  lineEdit()->setText(this->model()->data(new_index, ValueStringRole).toString());
955  manual_text_edited = false;
956  }
957 }
958 
959 int32_t C4DeepQComboBox::GetCurrentSelectionIndex()
960 {
961  QVariant selected_data = model()->data(model()->index(currentIndex(), 0, rootModelIndex()), OptionIndexRole);
962  if (selected_data.type() == QVariant::Int)
963  {
964  // Valid selection
965  return selected_data.toInt();
966  }
967  else
968  {
969  // Invalid selection
970  return -1;
971  }
972 }
973 
974 // event filter for view: Catch mouse clicks to prevent closing from simple mouse clicks
975 bool C4DeepQComboBox::eventFilter(QObject *obj, QEvent *event)
976 {
977  if (obj == view()->viewport())
978  {
979  if (event->type() == QEvent::MouseButtonPress)
980  {
981  QPoint pos = static_cast<QMouseEvent *>(event)->pos();
982  QModelIndex pressed_index = view()->indexAt(pos);
983  QRect item_rect = view()->visualRect(pressed_index);
984  // Check if a group was clicked
985  bool item_clicked = item_rect.contains(pos);
986  if (item_clicked)
987  {
988  QVariant selected_data = model()->data(pressed_index, OptionIndexRole);
989  if (selected_data.type() != QVariant::Int)
990  {
991  // This is a group. Just expand that entry.
992  QTreeView *tview = static_cast<QTreeView *>(view());
993  if (!tview->isExpanded(pressed_index))
994  {
995  tview->setExpanded(pressed_index, true);
996  int32_t child_row_count = model()->rowCount(pressed_index);
997  tview->scrollTo(model()->index(child_row_count - 1, 0, pressed_index), QAbstractItemView::EnsureVisible);
998  tview->scrollTo(pressed_index, QAbstractItemView::EnsureVisible);
999  }
1000  is_next_close_blocked = true;
1001  return true;
1002  }
1003  }
1004  else
1005  {
1006  is_next_close_blocked = true;
1007  return false;
1008  }
1009  // Delegate handling: The forward to delegate screws up for me sometimes and just stops randomly
1010  // Prevent this by calling the event directly
1011  QStyleOptionViewItem option;
1012  option.rect = view()->visualRect(pressed_index);
1013  if (item_delegate->editorEvent(event, model(), option, pressed_index))
1014  {
1015  // If the down event is taken by a music play event, ignore the following button up
1016  is_next_close_blocked = true;
1017  return true;
1018  }
1019  }
1020  else if (event->type() == QEvent::MouseButtonRelease)
1021  {
1022  if (is_next_close_blocked)
1023  {
1024  is_next_close_blocked = false;
1025  return true;
1026  }
1027  }
1028  }
1029  return QComboBox::eventFilter(obj, event);
1030 }
1031 
1032 
1033 /* Enumeration delegate editor */
1034 
1035 void C4PropertyDelegateEnumEditor::paintEvent(QPaintEvent *ev)
1036 {
1037  // Draw self
1038  QWidget::paintEvent(ev);
1039  // Draw shape widget
1040  if (paint_parameter_delegate && parameter_widget)
1041  {
1042  QPainter p(this);
1043  QStyleOptionViewItem view_item;
1044  view_item.rect.setTopLeft(parameter_widget->mapToParent(parameter_widget->rect().topLeft()));
1045  view_item.rect.setBottomRight(parameter_widget->mapToParent(parameter_widget->rect().bottomRight()));
1046  paint_parameter_delegate->Paint(&p, view_item, last_parameter_val);
1047  //p.fillRect(view_item.rect, QColor("red"));
1048  }
1049 }
1050 
1051 
1052 /* Enumeration (dropdown list) delegate */
1053 
1054 C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, C4PropList *props, const C4ValueArray *poptions)
1055  : C4PropertyDelegate(factory, props), allow_editing(false), sorted(false)
1056 {
1057  // Build enum options from C4Value definitions in script
1058  if (!poptions && props) poptions = props->GetPropertyArray(P_Options);
1059  C4String *default_option_key, *default_value_key = nullptr;
1060  if (props)
1061  {
1062  default_option_key = props->GetPropertyStr(P_OptionKey);
1063  default_value_key = props->GetPropertyStr(P_ValueKey);
1064  allow_editing = props->GetPropertyBool(P_AllowEditing);
1065  empty_name = props->GetPropertyStr(P_EmptyName);
1066  sorted = props->GetPropertyBool(P_Sorted);
1067  default_option.option_key = default_option_key;
1068  default_option.value_key = default_value_key;
1069  }
1070  if (poptions)
1071  {
1072  options.reserve(poptions->GetSize());
1073  for (int32_t i = 0; i < poptions->GetSize(); ++i)
1074  {
1075  const C4Value &v = poptions->GetItem(i);
1076  C4PropList *props = v.getPropList();
1077  if (!props) continue;
1078  Option option;
1079  option.props.SetPropList(props);
1080  option.name = props->GetPropertyStr(P_Name);
1081  if (!option.name) option.name = ::Strings.RegString("???");
1082  option.help = props->GetPropertyStr(P_EditorHelp);
1083  option.group = props->GetPropertyStr(P_Group);
1084  option.value_key = props->GetPropertyStr(P_ValueKey);
1085  if (!option.value_key) option.value_key = default_value_key;
1086  props->GetProperty(P_Value, &option.value);
1087  if (option.value.GetType() == C4V_Nil && empty_name) option.name = empty_name.Get();
1088  option.short_name = props->GetPropertyStr(P_ShortName);
1089  if (!option.short_name) option.short_name = option.name.Get();
1090  props->GetProperty(P_DefaultValueFunction, &option.value_function);
1091  option.type = C4V_Type(props->GetPropertyInt(P_Type, C4V_Any));
1092  option.option_key = props->GetPropertyStr(P_OptionKey);
1093  if (!option.option_key) option.option_key = default_option_key;
1094  // Derive storage type from given elements in delegate definition
1095  if (option.type != C4V_Any)
1096  option.storage_type = Option::StorageByType;
1097  else if (option.option_key && option.value.GetType() != C4V_Nil)
1098  option.storage_type = Option::StorageByKey;
1099  else
1100  option.storage_type = Option::StorageByValue;
1101  // Child delegate for value (resolved at runtime because there may be circular references)
1102  props->GetProperty(P_Delegate, &option.adelegate_val);
1103  option.priority = props->GetPropertyInt(P_Priority);
1104  option.force_serialization = props->GetPropertyInt(P_ForceSerialization);
1105  options.push_back(option);
1106  }
1107  }
1108 }
1109 
1110 QStandardItemModel *C4PropertyDelegateEnum::CreateOptionModel() const
1111 {
1112  // Create a QStandardItemModel tree from all options and their groups
1113  std::unique_ptr<QStandardItemModel> model(new QStandardItemModel());
1114  model->setColumnCount(1);
1115  int idx = 0;
1116  for (const Option &opt : options)
1117  {
1118  QStandardItem *new_item = model->invisibleRootItem(), *parent = nullptr;
1119  QStringList group_names;
1120  if (opt.group) group_names.append(QString(opt.group->GetCStr()).split(QString("/")));
1121  group_names.append(opt.name->GetCStr());
1122  for (const QString &group_name : group_names)
1123  {
1124  parent = new_item;
1125  int row_index = -1;
1126  for (int check_row_index = 0; check_row_index < new_item->rowCount(); ++check_row_index)
1127  if (new_item->child(check_row_index, 0)->text() == group_name)
1128  {
1129  row_index = check_row_index;
1130  new_item = new_item->child(check_row_index, 0);
1131  break;
1132  }
1133  if (row_index < 0)
1134  {
1135  QStandardItem *new_group = new QStandardItem(group_name);
1136  if (sorted)
1137  {
1138  // Groups always sorted by name. Could also sort by priority of highest priority element?
1139  new_group->setData("010000000"+group_name, C4DeepQComboBox::PriorityNameSortRole);
1140  }
1141  new_item->appendRow(new_group);
1142  new_item = new_group;
1143  }
1144  }
1145  // If this item is already set, add a duplicate entry
1146  if (new_item->data(C4DeepQComboBox::OptionIndexRole).isValid())
1147  {
1148  new_item = new QStandardItem(QString(opt.name->GetCStr()));
1149  parent->appendRow(new_item);
1150  }
1151  // Sort key
1152  if (sorted)
1153  {
1154  // Reverse priority and make positive, so we can sort by descending priority but ascending name
1155  new_item->setData(QString(FormatString("%09d%s", (int)(10000000-opt.priority), opt.name->GetCStr()).getData()), C4DeepQComboBox::PriorityNameSortRole);
1156  }
1157  new_item->setData(QVariant(idx), C4DeepQComboBox::OptionIndexRole);
1158  C4Object *item_obj_data = opt.value.getObj();
1159  if (item_obj_data) new_item->setData(QVariant(item_obj_data->Number), C4DeepQComboBox::ObjectHighlightRole);
1160  QString help = QString((opt.help ? opt.help : opt.name)->GetCStr());
1161  new_item->setData(help.replace('|', '\n'), Qt::ToolTipRole);
1162  if (opt.help) new_item->setData(QIcon(":/editor/res/Help.png"), Qt::DecorationRole);
1163  if (opt.sound_name) new_item->setData(QIcon(":/editor/res/Sound.png"), Qt::DecorationRole);
1164  if (allow_editing)
1165  {
1166  C4String *s = opt.value.getStr();
1167  new_item->setData(QString(s ? s->GetCStr() : ""), C4DeepQComboBox::ValueStringRole);
1168  }
1169  ++idx;
1170  }
1171  // Sort model and all groups
1172  if (sorted)
1173  {
1174  model->setSortRole(C4DeepQComboBox::PriorityNameSortRole);
1175  model->sort(0, Qt::AscendingOrder);
1176  }
1177  return model.release();
1178 }
1179 
1180 void C4PropertyDelegateEnum::ClearOptions()
1181 {
1182  options.clear();
1183 }
1184 
1185 void C4PropertyDelegateEnum::ReserveOptions(int32_t num)
1186 {
1187  options.reserve(num);
1188 }
1189 
1190 void C4PropertyDelegateEnum::AddTypeOption(C4String *name, C4V_Type type, const C4Value &val, C4PropertyDelegate *adelegate)
1191 {
1192  Option option;
1193  option.name = name;
1194  option.short_name = name;
1195  option.type = type;
1196  option.value = val;
1197  option.storage_type = Option::StorageByType;
1198  option.adelegate = adelegate;
1199  options.push_back(option);
1200 }
1201 
1202 void C4PropertyDelegateEnum::AddConstOption(C4String *name, const C4Value &val, C4String *group, C4String *sound_name)
1203 {
1204  Option option;
1205  option.name = name;
1206  option.short_name = name;
1207  option.group = group;
1208  option.value = val;
1209  option.storage_type = Option::StorageByValue;
1210  if (sound_name)
1211  {
1212  option.sound_name = sound_name;
1213  option.help = sound_name;
1214  }
1215  options.push_back(option);
1216 }
1217 
1218 int32_t C4PropertyDelegateEnum::GetOptionByValue(const C4Value &val) const
1219 {
1220  int32_t iopt = 0;
1221  bool match = false;
1222  for (auto &option : options)
1223  {
1224  switch (option.storage_type)
1225  {
1226  case Option::StorageByType:
1227  match = (val.GetTypeEx() == option.type);
1228  break;
1229  case Option::StorageByValue:
1230  match = (val == option.value);
1231  break;
1232  case Option::StorageByKey: // Compare value to value in property. Assume undefined as nil.
1233  {
1234  C4PropList *props = val.getPropList();
1235  C4PropList *def_props = option.value.getPropList();
1236  if (props && def_props)
1237  {
1238  C4Value propval, defval;
1239  props->GetPropertyByS(option.option_key.Get(), &propval);
1240  def_props->GetPropertyByS(option.option_key.Get(), &defval);
1241  match = (defval == propval);
1242  }
1243  break;
1244  }
1245  default: break;
1246  }
1247  if (match) break;
1248  ++iopt;
1249  }
1250  // If no option matches, return sentinel value
1251  return match ? iopt : Editor::INDEX_Custom_Value;
1252 }
1253 
1254 void C4PropertyDelegateEnum::UpdateEditorParameter(C4PropertyDelegateEnum::Editor *editor, bool by_selection) const
1255 {
1256  // Recreate parameter settings editor associated with the currently selected option of an enum
1257  if (editor->parameter_widget)
1258  {
1259  editor->parameter_widget->deleteLater();
1260  editor->parameter_widget = nullptr;
1261  }
1262  editor->paint_parameter_delegate = nullptr;
1263  int32_t idx = editor->last_selection_index;
1264  if (by_selection)
1265  {
1266  idx = editor->option_box->GetCurrentSelectionIndex();
1267  }
1268  // No parameter delegate if not a known option (custom text or invalid value)
1269  if (idx < 0 || idx >= options.size()) return;
1270  const Option &option = options[idx];
1271  // Lazy-resolve parameter delegate
1272  EnsureOptionDelegateResolved(option);
1273  // Create editor if needed
1274  if (option.adelegate)
1275  {
1276  // Determine value to be shown in editor
1277  C4Value parameter_val;
1278  if (!by_selection)
1279  {
1280  // Showing current selection: From last_val assigned in SetEditorData or by custom text
1281  parameter_val = editor->last_val;
1282  }
1283  else
1284  {
1285  // Selecting a new item: Set the default value
1286  parameter_val = option.value;
1287  // Although the default value is taken directly from SetEditorData, it needs to be set here to make child access into proplists and arrays possible
1288  // (note that actual setting is delayed by control queue and this may often the wrong value in some cases - the correct value will be shown on execution of the queue)
1289  SetOptionValue(editor->last_get_path, option, option.value);
1290  }
1291  // Resolve parameter value
1292  if (option.value_key)
1293  {
1294  C4Value child_val;
1295  C4PropList *props = parameter_val.getPropList();
1296  if (props) props->GetPropertyByS(option.value_key.Get(), &child_val);
1297  parameter_val = child_val;
1298  }
1299  // Show it
1300  editor->parameter_widget = option.adelegate->CreateEditor(factory, editor, QStyleOptionViewItem(), by_selection, true);
1301  if (editor->parameter_widget)
1302  {
1303  editor->layout->addWidget(editor->parameter_widget);
1304  C4PropertyPath delegate_value_path = editor->last_get_path;
1305  if (option.value_key) delegate_value_path = C4PropertyPath(delegate_value_path, option.value_key->GetCStr());
1306  option.adelegate->SetEditorData(editor->parameter_widget, parameter_val, delegate_value_path);
1307  // Forward editing signals
1308  connect(option.adelegate, &C4PropertyDelegate::EditorValueChangedSignal, editor->parameter_widget, [this, editor](QWidget *changed_editor)
1309  {
1310  if (changed_editor == editor->parameter_widget)
1311  if (!editor->updating)
1312  emit EditorValueChangedSignal(editor);
1313  });
1314  connect(option.adelegate, &C4PropertyDelegate::EditingDoneSignal, editor->parameter_widget, [this, editor](QWidget *changed_editor)
1315  {
1316  if (changed_editor == editor->parameter_widget) emit EditingDoneSignal(editor);
1317  });
1318  }
1319  else
1320  {
1321  // If the parameter widget is a shape display, show a dummy widget displaying the shape instead
1322  const C4PropertyDelegateShape *shape_delegate = option.adelegate->GetDirectShapeDelegate();
1323  if (shape_delegate)
1324  {
1325  // dummy widget that is not rendered. shape rendering is forwarded through own paint function
1326  editor->parameter_widget = new QWidget(editor);
1327  editor->layout->addWidget(editor->parameter_widget);
1328  editor->parameter_widget->setAttribute(Qt::WA_NoSystemBackground);
1329  editor->parameter_widget->setAttribute(Qt::WA_TranslucentBackground);
1330  editor->parameter_widget->setAttribute(Qt::WA_TransparentForMouseEvents);
1331  editor->paint_parameter_delegate = shape_delegate;
1332  editor->last_parameter_val = parameter_val;
1333  }
1334  }
1335  }
1336 }
1337 
1338 QModelIndex C4PropertyDelegateEnum::GetModelIndexByID(QStandardItemModel *model, QStandardItem *parent_item, int32_t id, const QModelIndex &parent) const
1339 {
1340  // Resolve data stored in model to model index in tree
1341  for (int row = 0; row < parent_item->rowCount(); ++row)
1342  {
1343  QStandardItem *child = parent_item->child(row, 0);
1344  QVariant v = child->data(C4DeepQComboBox::OptionIndexRole);
1345  if (v.type() == QVariant::Int && v.toInt() == id) return model->index(row, 0, parent);
1346  if (child->rowCount())
1347  {
1348  QModelIndex child_match = GetModelIndexByID(model, child, id, model->index(row, 0, parent));
1349  if (child_match.isValid()) return child_match;
1350  }
1351  }
1352  return QModelIndex();
1353 }
1354 
1355 void C4PropertyDelegateEnum::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
1356 {
1357  Editor *editor = static_cast<Editor*>(aeditor);
1358  editor->last_val = val;
1359  editor->last_get_path = property_path;
1360  editor->updating = true;
1361  // Update option selection
1362  int32_t index = GetOptionByValue(val);
1363  if (index == Editor::INDEX_Custom_Value && !allow_editing)
1364  {
1365  // Invalid value and no custom values allowed? Select first item.
1366  index = 0;
1367  }
1368  if (index == Editor::INDEX_Custom_Value)
1369  {
1370  // Custom value
1371  C4String *val_string = val.getStr();
1372  QString edit_string = val_string ? QString(val_string->GetCStr()) : QString(val.GetDataString().getData());
1373  editor->option_box->setEditText(edit_string);
1374  }
1375  else
1376  {
1377  // Regular enum entry
1378  QStandardItemModel *model = static_cast<QStandardItemModel *>(editor->option_box->model());
1379  editor->option_box->setCurrentModelIndex(GetModelIndexByID(model, model->invisibleRootItem(), index, QModelIndex()));
1380  }
1381  editor->last_selection_index = index;
1382  // Update parameter
1383  UpdateEditorParameter(editor, false);
1384  editor->updating = false;
1385  // Execute pending dropdowns from creation as child enums
1386  if (editor->dropdown_pending)
1387  {
1388  editor->dropdown_pending = false;
1389  QMetaObject::invokeMethod(editor->option_box, "doShowPopup", Qt::QueuedConnection);
1390  editor->option_box->showPopup();
1391  }
1392 }
1393 
1394 void C4PropertyDelegateEnum::SetModelData(QObject *aeditor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
1395 {
1396  // Fetch value from editor
1397  Editor *editor = static_cast<Editor*>(aeditor);
1398  /*QStandardItemModel *model = static_cast<QStandardItemModel *>(editor->option_box->model());
1399  QModelIndex selected_model_index = model->index(editor->option_box->currentIndex(), 0, editor->option_box->rootModelIndex());
1400  QVariant vidx = model->data(selected_model_index, C4DeepQComboBox::OptionIndexRole);
1401  if (vidx.type() != QVariant::Int) return;
1402  int32_t idx = vidx.toInt();
1403  if (idx < 0 || idx >= options.size()) return;*/
1404  int32_t idx = editor->last_selection_index;
1405  const Option *option;
1406  const C4Value *option_value;
1407  if (idx < 0)
1408  {
1409  option = &default_option;
1410  option_value = &editor->last_val;
1411  }
1412  else
1413  {
1414  option = &options[std::max<int32_t>(idx, 0)];
1415  option_value = &option->value;
1416  }
1417  // Store directly in value or in a proplist field?
1418  C4PropertyPath use_path;
1419  if (option->value_key.Get())
1420  use_path = C4PropertyPath(property_path, option->value_key->GetCStr());
1421  else
1422  use_path = property_path;
1423  // Value from a parameter or directly from the enum?
1424  if (option->adelegate)
1425  {
1426  // Default value on enum change (on main path; not use_path because the default value is always given as the whole proplist)
1427  if (editor->option_changed) SetOptionValue(property_path, *option, *option_value);
1428  // Value from a parameter.
1429  // Using a setter function?
1430  use_path = option->adelegate->GetPathForProperty(use_path, nullptr);
1431  option->adelegate->SetModelData(editor->parameter_widget, use_path, prop_shape);
1432  }
1433  else
1434  {
1435  // No parameter. Use value.
1436  if (editor->option_changed) SetOptionValue(property_path, *option, *option_value);
1437  }
1438  editor->option_changed = false;
1439 }
1440 
1441 void C4PropertyDelegateEnum::SetOptionValue(const C4PropertyPath &use_path, const C4PropertyDelegateEnum::Option &option, const C4Value &option_value) const
1442 {
1443  // After an enum entry has been selected, set its value
1444  // Either directly by value or through a function
1445  // Get serialization base
1446  const C4PropList *ignore_base_props;
1447  if (option.force_serialization)
1448  {
1449  ignore_base_props = option_value.getPropList();
1450  if (ignore_base_props) ignore_base_props = (ignore_base_props->IsStatic() ? ignore_base_props->IsStatic()->GetParent() : nullptr);
1451  }
1452  else
1453  {
1454  ignore_base_props = option.props.getPropList();
1455  }
1456  const C4PropListStatic *ignore_base_props_static = ignore_base_props ? ignore_base_props->IsStatic() : nullptr;
1457  if (option.value_function.GetType() == C4V_Function)
1458  {
1459  use_path.SetProperty(FormatString("Call(%s, %s, %s)", option.value_function.GetDataString().getData(), use_path.GetRoot(), option_value.GetDataString(20, ignore_base_props_static).getData()).getData());
1460  }
1461  else
1462  {
1463  C4PropList *option_props = option.props.getPropList();
1464  use_path.SetProperty(option_value, ignore_base_props_static);
1465  }
1466  factory->GetPropertyModel()->DoOnUpdateCall(use_path, this);
1467 }
1468 
1469 QWidget *C4PropertyDelegateEnum::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
1470 {
1471  Editor *editor = new Editor(parent);
1472  editor->layout = new QHBoxLayout(editor);
1473  editor->layout->setContentsMargins(0, 0, 0, 0);
1474  editor->layout->setMargin(0);
1475  editor->layout->setSpacing(0);
1476  editor->updating = true;
1477  editor->option_box = new C4DeepQComboBox(editor, GetOptionComboBoxButtonType(), allow_editing);
1478  editor->layout->addWidget(editor->option_box);
1479  for (auto &option : options) editor->option_box->addItem(option.name->GetCStr());
1480  editor->option_box->setModel(CreateOptionModel());
1481  editor->option_box->model()->setParent(editor->option_box);
1482  // Signal for selecting a new entry from the dropdown menu
1483  connect(editor->option_box, &C4DeepQComboBox::NewItemSelected, editor, [editor, this](int32_t newval) {
1484  if (!editor->updating) this->UpdateOptionIndex(editor, newval, nullptr); });
1485  // Signal for write-in on enum delegates that allow editing
1486  if (allow_editing)
1487  {
1488  connect(editor->option_box, &C4DeepQComboBox::TextChanged, editor, [editor, this](const QString &new_text) {
1489  if (!editor->updating)
1490  {
1491  this->UpdateOptionIndex(editor, GetOptionByValue(C4VString(new_text.toUtf8())), &new_text);
1492  }
1493  });
1494  }
1495 
1496  editor->updating = false;
1497  // If created by a selection from a parent enum, show drop down immediately after value has been set
1498  editor->dropdown_pending = by_selection && is_child;
1499  return editor;
1500 }
1501 
1502 void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *editor, int newval, const QString *custom_text) const
1503 {
1504  bool has_changed = false;
1505  // Change by text entry?
1506  if (custom_text)
1507  {
1508  C4String *last_value_string = editor->last_val.getStr();
1509  if (!last_value_string || last_value_string->GetData() != custom_text->toUtf8())
1510  {
1511  editor->last_val = C4VString(custom_text->toUtf8());
1512  has_changed = true;
1513  }
1514  }
1515  // Update value and parameter delegate if selection changed
1516  if (newval != editor->last_selection_index)
1517  {
1518  editor->last_selection_index = newval;
1519  UpdateEditorParameter(editor, !custom_text);
1520  has_changed = true;
1521  }
1522  // Change either by text entry or by dropdown selection: Emit signal to parent
1523  if (has_changed)
1524  {
1525  editor->option_changed = true;
1526  emit EditorValueChangedSignal(editor);
1527  }
1528 }
1529 
1530 void C4PropertyDelegateEnum::EnsureOptionDelegateResolved(const Option &option) const
1531 {
1532  // Lazy-resolve parameter delegate
1533  if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil)
1534  option.adelegate = factory->GetDelegateByValue(option.adelegate_val);
1535 }
1536 
1537 QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1538 {
1539  // Display string from value
1540  int32_t idx = GetOptionByValue(v);
1541  if (idx == Editor::INDEX_Custom_Value)
1542  {
1543  // Value not found: Default display of strings; full display of nonsense values for debugging purposes.
1544  C4String *custom_string = v.getStr();
1545  if (custom_string)
1546  {
1547  return QString(custom_string->GetCStr());
1548  }
1549  else
1550  {
1551  return C4PropertyDelegate::GetDisplayString(v, obj, short_names);
1552  }
1553  }
1554  else
1555  {
1556  // Value found: Display option string plus parameter
1557  const Option &option = options[idx];
1558  QString result = (short_names ? option.short_name : option.name)->GetCStr();
1559  // Lazy-resolve parameter delegate
1560  EnsureOptionDelegateResolved(option);
1561  if (option.adelegate)
1562  {
1563  C4Value param_val = v;
1564  if (option.value_key.Get())
1565  {
1566  param_val.Set0();
1567  C4PropList *vp = v.getPropList();
1568  if (vp) vp->GetPropertyByS(option.value_key, &param_val);
1569  }
1570  if (!result.isEmpty()) result += " ";
1571  result += option.adelegate->GetDisplayString(param_val, obj, short_names);
1572  }
1573  return result;
1574  }
1575 }
1576 
1577 const C4PropertyDelegateShape *C4PropertyDelegateEnum::GetShapeDelegate(C4Value &val, C4PropertyPath *shape_path) const
1578 {
1579  // Does this delegate own a shape? Forward decision into selected option.
1580  int32_t option_idx = GetOptionByValue(val);
1581  if (option_idx == Editor::INDEX_Custom_Value) return nullptr;
1582  const Option &option = options[option_idx];
1583  EnsureOptionDelegateResolved(option);
1584  if (!option.adelegate) return nullptr;
1585  if (option.value_key.Get())
1586  {
1587  *shape_path = option.adelegate->GetPathForProperty(*shape_path, option.value_key->GetCStr());
1588  C4PropList *vp = val.getPropList();
1589  val.Set0();
1590  if (vp) vp->GetPropertyByS(option.value_key, &val);
1591  }
1592  return option.adelegate->GetShapeDelegate(val, shape_path);
1593 }
1594 
1595 bool C4PropertyDelegateEnum::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const
1596 {
1597  // Custom painting: Forward to selected child delegate
1598  int32_t option_idx = GetOptionByValue(val);
1599  if (option_idx == Editor::INDEX_Custom_Value) return false;
1600  const Option &selected_option = options[option_idx];
1601  EnsureOptionDelegateResolved(selected_option);
1602  if (!selected_option.adelegate) return false;
1603  if (selected_option.adelegate->HasCustomPaint())
1604  {
1605  QStyleOptionViewItem parameter_option = QStyleOptionViewItem(option);
1606  parameter_option.rect.adjust(parameter_option.rect.width()/2, 0, 0, 0);
1607  C4Value parameter_val = val;
1608  if (selected_option.value_key.Get())
1609  {
1610  parameter_val.Set0();
1611  C4PropList *vp = val.getPropList();
1612  if (vp) vp->GetPropertyByS(selected_option.value_key, &parameter_val);
1613  }
1614  selected_option.adelegate->Paint(painter, parameter_option, parameter_val);
1615  }
1616  // Always return false to draw self using the standard method
1617  return false;
1618 }
1619 
1620 bool C4PropertyDelegateEnum::IsPasteValid(const C4Value &val) const
1621 {
1622  // Strings always OK in editable enums
1623  if (val.GetType() == C4V_String && allow_editing) return true;
1624  // Must be a valid selection
1625  int32_t option_idx = GetOptionByValue(val);
1626  if (option_idx == Editor::INDEX_Custom_Value) return false;
1627  const Option &option = options[option_idx];
1628  // Check validity for parameter
1629  EnsureOptionDelegateResolved(option);
1630  if (!option.adelegate) return true; // No delegate? Then any value is OK.
1631  C4Value parameter_val;
1632  if (option.value_key.Get())
1633  {
1634  C4PropList *vp = val.getPropList();
1635  if (!vp) return false;
1636  vp->GetPropertyByS(option.value_key, &parameter_val); // if this fails, check parameter against nil
1637  }
1638  else
1639  {
1640  parameter_val = val;
1641  }
1642  return option.adelegate->IsPasteValid(parameter_val);
1643 }
1644 
1645 
1646 /* Definition delegate */
1647 
1648 C4PropertyDelegateDef::C4PropertyDelegateDef(const C4PropertyDelegateFactory *factory, C4PropList *props)
1649  : C4PropertyDelegateEnum(factory, props)
1650 {
1651  // nil is always an option
1652  AddConstOption(empty_name ? empty_name.Get() : ::Strings.RegString("nil"), C4VNull);
1653  // Collect sorted definitions
1654  filter_property = props ? props->GetPropertyStr(P_Filter) : nullptr;
1655  if (filter_property)
1656  {
1657  // With filter just create a flat list
1658  std::vector<C4Def *> defs = ::Definitions.GetAllDefs(filter_property);
1659  std::sort(defs.begin(), defs.end(), [](C4Def *a, C4Def *b) -> bool {
1660  return strcmp(a->GetName(), b->GetName()) < 0;
1661  });
1662  // Add them
1663  for (C4Def *def : defs)
1664  {
1665  C4RefCntPointer<C4String> option_name = ::Strings.RegString(FormatString("%s (%s)", def->id.ToString(), def->GetName()));
1666  AddConstOption(option_name, C4Value(def), nullptr);
1667  }
1668  }
1669  else
1670  {
1671  // Without filter copy tree from definition list model
1672  C4ConsoleQtDefinitionListModel *def_list_model = factory->GetDefinitionListModel();
1673  // Recursively add all defs from model
1674  AddDefinitions(def_list_model, QModelIndex(), nullptr);
1675  }
1676 }
1677 
1678 void C4PropertyDelegateDef::AddDefinitions(C4ConsoleQtDefinitionListModel *def_list_model, QModelIndex parent, C4String *group)
1679 {
1680  int32_t count = def_list_model->rowCount(parent);
1681  for (int32_t i = 0; i < count; ++i)
1682  {
1683  QModelIndex index = def_list_model->index(i, 0, parent);
1684  C4Def *def = def_list_model->GetDefByModelIndex(index);
1685  C4RefCntPointer<C4String> name = ::Strings.RegString(def_list_model->GetNameByModelIndex(index));
1686  if (def) AddConstOption(name.Get(), C4Value(def), group);
1687  if (def_list_model->rowCount(index))
1688  {
1689  AddDefinitions(def_list_model, index, group ? ::Strings.RegString(FormatString("%s/%s", group->GetCStr(), name->GetCStr()).getData()) : name.Get());
1690  }
1691  }
1692 }
1693 
1694 bool C4PropertyDelegateDef::IsPasteValid(const C4Value &val) const
1695 {
1696  // Must be a definition or nil
1697  if (val.GetType() == C4V_Nil) return true;
1698  C4Def *def = val.getDef();
1699  if (!def) return false;
1700  // Check filter
1701  if (filter_property)
1702  {
1703  C4Value prop_val;
1704  if (!def->GetPropertyByS(filter_property, &prop_val)) return false;
1705  if (!prop_val) return false;
1706  }
1707  return true;
1708 }
1709 
1710 
1711 /* Object delegate */
1712 
1713 C4PropertyDelegateObject::C4PropertyDelegateObject(const C4PropertyDelegateFactory *factory, C4PropList *props)
1714  : C4PropertyDelegateEnum(factory, props), max_nearby_objects(20)
1715 {
1716  // Settings
1717  if (props)
1718  {
1719  filter = props->GetPropertyStr(P_Filter);
1720  }
1721  // Actual object list is created/updated when the editor is created
1722 }
1723 
1724 C4RefCntPointer<C4String> C4PropertyDelegateObject::GetObjectEntryString(C4Object *obj) const
1725 {
1726  // Compose object display string from containment(*), name, position (@x,y) and object number (#n)
1727  return ::Strings.RegString(FormatString("%s%s @%d,%d (#%d)", obj->Contained ? "*" : "", obj->GetName(), (int)obj->GetX(), (int)obj->GetY(), (int)obj->Number));
1728 }
1729 
1730 void C4PropertyDelegateObject::UpdateObjectList()
1731 {
1732  // Re-create object list from current position
1733  ClearOptions();
1734  // Get matching objects first
1735  std::vector<C4Object *> objects;
1736  for (C4Object *obj : ::Objects) if (obj->Status)
1737  {
1738  C4Value filter_val;
1739  if (filter)
1740  {
1741  if (!obj->GetPropertyByS(filter, &filter_val)) continue;
1742  if (!filter_val) continue;
1743  }
1744  objects.push_back(obj);
1745  }
1746  // Get list sorted by distance from selected object
1747  std::vector<C4Object *> objects_by_distance;
1748  int32_t cx=0, cy=0;
1750  {
1751  objects_by_distance = objects;
1752  auto ObjDist = [cx, cy](C4Object *o) { return (o->GetX() - cx)*(o->GetX() - cx) + (o->GetY() - cy)*(o->GetY() - cy); };
1753  std::stable_sort(objects_by_distance.begin(), objects_by_distance.end(), [&ObjDist](C4Object *a, C4Object *b) { return ObjDist(a) < ObjDist(b); });
1754  }
1755  size_t num_nearby = objects_by_distance.size();
1756  bool has_all_objects_list = (num_nearby > max_nearby_objects);
1757  if (has_all_objects_list) num_nearby = max_nearby_objects;
1758  // Add actual objects
1759  ReserveOptions(1 + num_nearby + !!num_nearby + (has_all_objects_list ? objects.size() : 0));
1760  AddConstOption(::Strings.RegString("nil"), C4VNull); // nil is always an option
1761  if (num_nearby)
1762  {
1763  // TODO: "Select object" entry
1764  //AddCallbackOption(LoadResStr("IDS_CNS_SELECTOBJECT"));
1765  // Nearby list
1766  C4RefCntPointer<C4String> nearby_group;
1767  // If there are main objects, Create a subgroup. Otherwise, just put all elements into the main group.
1768  if (has_all_objects_list) nearby_group = ::Strings.RegString(LoadResStr("IDS_CNS_NEARBYOBJECTS"));
1769  for (int32_t i = 0; i < num_nearby; ++i)
1770  {
1771  C4Object *obj = objects_by_distance[i];
1772  AddConstOption(GetObjectEntryString(obj).Get(), C4VObj(obj), nearby_group.Get());
1773  }
1774  // All objects
1775  if (has_all_objects_list)
1776  {
1777  C4RefCntPointer<C4String> all_group = ::Strings.RegString(LoadResStr("IDS_CNS_ALLOBJECTS"));
1778  for (C4Object *obj : objects) AddConstOption(GetObjectEntryString(obj).Get(), C4VObj(obj), all_group.Get());
1779  }
1780  }
1781 }
1782 
1783 QWidget *C4PropertyDelegateObject::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
1784 {
1785  // Update object list for created editor
1786  // (This should be safe since the object delegate cannot contain nested delegates)
1787  const_cast<C4PropertyDelegateObject *>(this)->UpdateObjectList();
1788  return C4PropertyDelegateEnum::CreateEditor(parent_delegate, parent, option, by_selection, is_child);
1789 }
1790 
1791 QString C4PropertyDelegateObject::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1792 {
1793  C4Object *vobj = v.getObj();
1794  if (vobj)
1795  {
1796  C4RefCntPointer<C4String> s = GetObjectEntryString(vobj);
1797  return QString(s->GetCStr());
1798  }
1799  else
1800  {
1801  return QString(v.GetDataString().getData());
1802  }
1803 }
1804 
1805 bool C4PropertyDelegateObject::IsPasteValid(const C4Value &val) const
1806 {
1807  // Must be an object or nil
1808  if (val.GetType() == C4V_Nil) return true;
1809  C4Object *obj = val.getObj();
1810  if (!obj) return false;
1811  // Check filter
1812  if (filter)
1813  {
1814  C4Value prop_val;
1815  if (!obj->GetPropertyByS(filter, &prop_val)) return false;
1816  if (!prop_val) return false;
1817  }
1818  return true;
1819 }
1820 
1821 
1822 /* Sound delegate */
1823 
1824 C4PropertyDelegateSound::C4PropertyDelegateSound(const C4PropertyDelegateFactory *factory, C4PropList *props)
1825  : C4PropertyDelegateEnum(factory, props)
1826 {
1827  // Add none-option
1828  AddConstOption(::Strings.RegString("nil"), C4VNull);
1829  // Add all sounds as options
1830  for (C4SoundEffect *fx = ::Application.SoundSystem.GetFirstSound(); fx; fx = fx->Next)
1831  {
1832  // Extract group name as path to sound, replacing "::" by "/" for enum groups
1833  StdStrBuf full_name_s(fx->GetFullName(), true);
1834  RemoveExtension(&full_name_s);
1835  const char *full_name = full_name_s.getData();
1836  const char *base_name = full_name, *pos;
1837  StdStrBuf group_string;
1838  while ((pos = SSearch(base_name, "::")))
1839  {
1840  if (group_string.getLength()) group_string.AppendChar('/');
1841  group_string.Append(base_name, pos - base_name - 2);
1842  base_name = pos;
1843  }
1845  if (group_string.getLength()) group = ::Strings.RegString(group_string);
1846  // Script name: Full name (without extension)
1847  C4RefCntPointer<C4String> sound_string = ::Strings.RegString(full_name_s);
1848  // Add the option
1849  AddConstOption(::Strings.RegString(base_name), C4VString(sound_string.Get()), group.Get(), sound_string.Get());
1850  }
1851 }
1852 
1853 QString C4PropertyDelegateSound::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1854 {
1855  // Always show full sound name
1856  C4String *val_string = v.getStr();
1857  return val_string ? QString(val_string->GetCStr()) : QString(v.GetDataString().getData());
1858 }
1859 
1860 bool C4PropertyDelegateSound::IsPasteValid(const C4Value &val) const
1861 {
1862  // Must be nil or a string
1863  if (val.GetType() == C4V_Nil) return true;
1864  if (val.GetType() != C4V_String) return false;
1865  return true;
1866 }
1867 
1868 
1869 /* Boolean delegate */
1870 
1871 C4PropertyDelegateBool::C4PropertyDelegateBool(const C4PropertyDelegateFactory *factory, C4PropList *props)
1872  : C4PropertyDelegateEnum(factory, props)
1873 {
1874  // Add boolean options
1875  ReserveOptions(2);
1876  AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_FALSE")), C4VBool(false));
1877  AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_TRUE")), C4VBool(true));
1878 }
1879 
1880 bool C4PropertyDelegateBool::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
1881 {
1882  // Force value to bool
1883  bool success = C4PropertyDelegateEnum::GetPropertyValue(container, key, index, out_val);
1884  if (out_val->GetType() != C4V_Bool) *out_val = C4VBool(!!*out_val);
1885  return success;
1886 }
1887 
1888 bool C4PropertyDelegateBool::IsPasteValid(const C4Value &val) const
1889 {
1890  // Must be a boolean
1891  if (val.GetType() != C4V_Bool) return false;
1892  return true;
1893 }
1894 
1895 
1896 /* Has-effect delegate */
1897 
1898 C4PropertyDelegateHasEffect::C4PropertyDelegateHasEffect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
1899  : C4PropertyDelegateBool(factory, props)
1900 {
1901  if (props) effect = props->GetPropertyStr(P_Effect);
1902 }
1903 
1904 bool C4PropertyDelegateHasEffect::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
1905 {
1906  const C4Object *obj = container.getObj();
1907  if (obj && effect)
1908  {
1909  bool has_effect = false;
1910  for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
1911  if (!fx->IsDead())
1912  if (!strcmp(fx->GetName(), effect->GetCStr()))
1913  {
1914  has_effect = true;
1915  break;
1916  }
1917  *out_val = C4VBool(has_effect);
1918  return true;
1919  }
1920  return false;
1921 }
1922 
1923 
1924 /* C4Value via an enumeration delegate */
1925 
1926 C4PropertyDelegateC4ValueEnum::C4PropertyDelegateC4ValueEnum(const C4PropertyDelegateFactory *factory, C4PropList *props)
1927  : C4PropertyDelegateEnum(factory, props)
1928 {
1929  // Add default C4Value selections
1930  ReserveOptions(10);
1931  AddTypeOption(::Strings.RegString("nil"), C4V_Nil, C4VNull);
1932  AddTypeOption(::Strings.RegString("bool"), C4V_Bool, C4VNull, factory->GetDelegateByValue(C4VString("bool")));
1933  AddTypeOption(::Strings.RegString("int"), C4V_Int, C4VNull, factory->GetDelegateByValue(C4VString("int")));
1934  AddTypeOption(::Strings.RegString("string"), C4V_String, C4VNull, factory->GetDelegateByValue(C4VString("string")));
1935  AddTypeOption(::Strings.RegString("array"), C4V_Array, C4VNull, factory->GetDelegateByValue(C4VString("array")));
1936  AddTypeOption(::Strings.RegString("function"), C4V_Function, C4VNull, factory->GetDelegateByValue(C4VString("function")));
1937  AddTypeOption(::Strings.RegString("object"), C4V_Object, C4VNull, factory->GetDelegateByValue(C4VString("object")));
1938  AddTypeOption(::Strings.RegString("def"), C4V_Def, C4VNull, factory->GetDelegateByValue(C4VString("def")));
1939  AddTypeOption(::Strings.RegString("effect"), C4V_Effect, C4VNull, factory->GetDelegateByValue(C4VString("effect")));
1940  AddTypeOption(::Strings.RegString("proplist"), C4V_PropList, C4VNull, factory->GetDelegateByValue(C4VString("proplist")));
1941 }
1942 
1943 
1944 /* C4Value via an edit field delegate */
1945 
1946 C4PropertyDelegateC4ValueInputEditor::C4PropertyDelegateC4ValueInputEditor(QWidget *parent)
1947  : QWidget(parent), layout(nullptr), edit(nullptr), extended_button(nullptr), commit_pending(false)
1948 {
1949  layout = new QHBoxLayout(this);
1950  layout->setContentsMargins(0, 0, 0, 0);
1951  layout->setMargin(0);
1952  layout->setSpacing(0);
1953  edit = new QLineEdit(this);
1954  layout->addWidget(edit);
1955  extended_button = new QPushButton("...", this);
1956  extended_button->setMaximumWidth(extended_button->fontMetrics().boundingRect("...").width() + 6);
1957  layout->addWidget(extended_button);
1958  extended_button->hide();
1959  edit->setFocus();
1960  setLayout(layout);
1961 }
1962 
1963 void C4PropertyDelegateC4ValueInput::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
1964 {
1965  Editor *editor = static_cast<Editor *>(aeditor);
1966  editor->edit->setText(val.GetDataString().getData());
1967  if (val.GetType() == C4V_PropList || val.GetType() == C4V_Array)
1968  {
1969  editor->extended_button->show();
1970  editor->property_path = property_path;
1971  }
1972  else
1973  {
1974  editor->extended_button->hide();
1975  }
1976 }
1977 
1978 void C4PropertyDelegateC4ValueInput::SetModelData(QObject *aeditor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
1979 {
1980  // Only set model data when pressing Enter explicitely; not just when leaving
1981  Editor *editor = static_cast<Editor *>(aeditor);
1982  if (editor->commit_pending)
1983  {
1984  property_path.SetProperty(editor->edit->text().toUtf8());
1985  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
1986  editor->commit_pending = false;
1987  }
1988 }
1989 
1990 QWidget *C4PropertyDelegateC4ValueInput::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
1991 {
1992  // Editor is just an edit box plus a "..." button for array/proplist types
1993  Editor *editor = new Editor(parent);
1994  // EditingDone only on Return; not just when leaving edit field
1995  connect(editor->edit, &QLineEdit::returnPressed, editor, [this, editor]() {
1996  editor->commit_pending = true;
1997  emit EditingDoneSignal(editor);
1998  });
1999  connect(editor->extended_button, &QPushButton::pressed, editor, [this, editor]() {
2000  C4Value val = editor->property_path.ResolveValue();
2001  if (val.getPropList() || val.getArray())
2002  {
2003  this->factory->GetPropertyModel()->DescendPath(val, val.getPropList(), editor->property_path);
2004  ::Console.EditCursor.InvalidateSelection();
2005  }
2006  });
2007  // Selection in child enum: Direct focus
2008  if (by_selection && is_child) editor->edit->setFocus();
2009  return editor;
2010 }
2011 
2012 
2013 /* Areas shown in viewport */
2014 
2015 C4PropertyDelegateShape::C4PropertyDelegateShape(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2016  : C4PropertyDelegate(factory, props), clr(0xffff0000)
2017 {
2018  if (props)
2019  {
2020  clr = props->GetPropertyInt(P_Color) | 0xff000000;
2021  }
2022 }
2023 
2024 void C4PropertyDelegateShape::SetModelData(QObject *editor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
2025 {
2026  // Only set shape data if triggered through shape movement signal; ignore update calls from e.g. parent enum editor
2027  if (!editor)
2028  {
2029  if (prop_shape && prop_shape->GetParentDelegate() == this)
2030  {
2031  property_path.SetProperty(prop_shape->GetValue());
2032  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
2033  }
2034  }
2035 }
2036 
2037 bool C4PropertyDelegateShape::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const
2038 {
2039  // Background color
2040  if (option.state & QStyle::State_Selected)
2041  painter->fillRect(option.rect, option.palette.highlight());
2042  else
2043  painter->fillRect(option.rect, option.palette.base());
2044  // Draw a frame in shape color
2045  painter->save();
2046  QColor frame_color = QColor(QRgb(clr & 0xffffff));
2047  int32_t width = Clamp<int32_t>(option.rect.height() / 8, 2, 6) &~1;
2048  QPen rect_pen(QBrush(frame_color), width, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
2049  painter->setPen(rect_pen);
2050  QRect inner_rect = option.rect.adjusted(width / 2, width / 2, -width / 2, -width / 2);
2051  if (inner_rect.width() > inner_rect.height())
2052  {
2053  // Draw shape in right corner
2054  inner_rect.adjust(inner_rect.width() - inner_rect.height(), 0, 0, 0);
2055  }
2056  // Paint by shape type
2057  DoPaint(painter, inner_rect);
2058  // Done painting
2059  painter->restore();
2060  return true;
2061 }
2062 
2063 void C4PropertyDelegateShape::ConnectSignals(C4ConsoleQtShape *shape, const C4PropertyPath &property_path) const
2064 {
2065  connect(shape, &C4ConsoleQtShape::ShapeDragged, this, [this, shape, property_path]() {
2066  this->SetModelData(nullptr, property_path, shape);
2067  });
2068 }
2069 
2070 /* Areas shown in viewport: Rectangle */
2071 
2072 C4PropertyDelegateRect::C4PropertyDelegateRect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2073  : C4PropertyDelegateShape(factory, props)
2074 {
2075  if (props)
2076  {
2077  storage = props->GetPropertyStr(P_Storage);
2078  }
2079 }
2080 
2081 void C4PropertyDelegateRect::DoPaint(QPainter *painter, const QRect &inner_rect) const
2082 {
2083  painter->drawRect(inner_rect);
2084 }
2085 
2086 bool C4PropertyDelegateRect::IsPasteValid(const C4Value &val) const
2087 {
2088  // Check storage as prop list
2089  if (storage)
2090  {
2091  // Proplist-stored rect must have defined properties
2092  C4PropertyName def_property_names[2][4] = { { P_x, P_y, P_wdt, P_hgt },{ P_X, P_Y, P_Wdt, P_Hgt } };
2093  C4PropertyName *property_names = nullptr;
2094  if (storage == &::Strings.P[P_proplist])
2095  {
2096  property_names = def_property_names[0];
2097  }
2098  else if (storage == &::Strings.P[P_Proplist])
2099  {
2100  property_names = def_property_names[1];
2101  }
2102  if (property_names)
2103  {
2104  C4PropList *val_proplist = val.getPropList();
2105  if (!val_proplist) return false;
2106  for (int32_t i = 0; i < 4; ++i)
2107  {
2108  C4Value propval;
2109  if (!val_proplist->GetProperty(property_names[i], &propval)) return false;
2110  if (propval.GetType() != C4V_Int) return false;
2111  }
2112  // extra properties are OK
2113  }
2114  return true;
2115  }
2116  // Check storage as array: Expect array with four elements. Width and height non-negative.
2117  C4ValueArray *val_arr = val.getArray();
2118  if (!val_arr || val_arr->GetSize() != 4) return false;
2119  for (int32_t i = 0; i < 4; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2120  if (val_arr->GetItem(2)._getInt() < 0) return false;
2121  if (val_arr->GetItem(3)._getInt() < 0) return false;
2122  return true;
2123 }
2124 
2125 
2126 /* Areas shown in viewport: Circle */
2127 
2128 C4PropertyDelegateCircle::C4PropertyDelegateCircle(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2129  : C4PropertyDelegateShape(factory, props)
2130 {
2131  if (props)
2132  {
2133  can_move_center = props->GetPropertyBool(P_CanMoveCenter);
2134  }
2135 }
2136 
2137 void C4PropertyDelegateCircle::DoPaint(QPainter *painter, const QRect &inner_rect) const
2138 {
2139  painter->drawEllipse(inner_rect);
2140  if (can_move_center) painter->drawPoint(inner_rect.center());
2141 }
2142 
2143 bool C4PropertyDelegateCircle::IsPasteValid(const C4Value &val) const
2144 {
2145  // Circle radius stored as single non-negative int
2146  if (!can_move_center) return (val.GetType() == C4V_Int) && (val.getInt() >= 0);
2147  // Circle+Center stored as array with three elements (radius, x, y)
2148  C4ValueArray *val_arr = val.getArray();
2149  if (!val_arr || val_arr->GetSize() != 3) return false;
2150  for (int32_t i = 0; i < 3; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2151  if (val_arr->GetItem(0)._getInt() < 0) return false;
2152  return true;
2153 }
2154 
2155 
2156 /* Areas shown in viewport: Point */
2157 
2158 C4PropertyDelegatePoint::C4PropertyDelegatePoint(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2159  : C4PropertyDelegateShape(factory, props)
2160 {
2161 }
2162 
2163 void C4PropertyDelegatePoint::DoPaint(QPainter *painter, const QRect &inner_rect) const
2164 {
2165  QPoint ctr = inner_rect.center();
2166  int r = inner_rect.height() * 7 / 20;
2167  painter->drawLine(ctr + QPoint(-r, -r), ctr + QPoint(+r, +r));
2168  painter->drawLine(ctr + QPoint(+r, -r), ctr + QPoint(-r, +r));
2169  painter->drawEllipse(inner_rect);
2170 }
2171 
2172 bool C4PropertyDelegatePoint::IsPasteValid(const C4Value &val) const
2173 {
2174  // Point stored as array with two elements
2175  C4ValueArray *val_arr = val.getArray();
2176  if (!val_arr || val_arr->GetSize() != 2) return false;
2177  for (int32_t i = 0; i < 2; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2178  return true;
2179 }
2180 
2181 
2182 /* Areas shown in viewport: Graph */
2183 
2184 C4PropertyDelegateGraph::C4PropertyDelegateGraph(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2185  : C4PropertyDelegateShape(factory, props)
2186 {
2187  if (props)
2188  {
2189  horizontal_fix = props->GetPropertyBool(P_HorizontalFix);
2190  vertical_fix = props->GetPropertyBool(P_VerticalFix);
2191  structure_fix = props->GetPropertyBool(P_StructureFix);
2192  }
2193 }
2194 
2195 void C4PropertyDelegateGraph::DoPaint(QPainter *painter, const QRect &inner_rect) const
2196 {
2197  // Draw symbol as a bunch of connected lines
2198  QPoint ctr = inner_rect.center();
2199  int r = inner_rect.height() * 7 / 20;
2200  painter->drawLine(ctr, ctr + QPoint(-r / 2, -r));
2201  painter->drawLine(ctr, ctr + QPoint(+r / 2, -r));
2202  painter->drawLine(ctr, ctr + QPoint(0, +r));
2203 }
2204 
2205 bool C4PropertyDelegateGraph::IsVertexPasteValid(const C4Value &val) const
2206 {
2207  // Check that it's an array of at least one point
2208  const C4ValueArray *arr = val.getArray();
2209  if (!arr || !arr->GetSize()) return false;
2210  // Check validity of each point
2211  const int32_t n_props = 2;
2212  C4PropertyName property_names[n_props] = { P_X, P_Y };
2213  for (int32_t i_pt = 0; i_pt < arr->GetSize(); ++i_pt)
2214  {
2215  const C4Value &pt = arr->GetItem(i_pt);
2216  const C4PropList *ptp = pt.getPropList();
2217  if (!ptp) return false;
2218  for (int32_t i_prop = 0; i_prop < n_props; ++i_prop)
2219  {
2220  C4Value ptprop;
2221  if (!ptp->GetProperty(property_names[i_prop], &ptprop)) return false;
2222  if (ptprop.GetType() != C4V_Int) return false;
2223  }
2224  }
2225  return true;
2226 }
2227 
2228 bool C4PropertyDelegateGraph::IsEdgePasteValid(const C4Value &val) const
2229 {
2230  // Check that it's an array
2231  // Empty is OK; it could be a graph with one vertex and no edges
2232  const C4ValueArray *arr = val.getArray();
2233  if (!arr || !arr->GetSize()) return false;
2234  // Check validity of each edge
2235  for (int32_t i_pt = 0; i_pt < arr->GetSize(); ++i_pt)
2236  {
2237  const C4Value pt = arr->GetItem(i_pt);
2238  const C4ValueArray *pta;
2239  const C4PropList *ptp = pt.getPropList();
2240  if (!ptp) return false;
2241  pta = ptp->GetPropertyArray(P_Vertices);
2242  if (!pta) return false;
2243  // Needs two vertices (may have more values which are ignored)
2244  if (pta->GetSize() < 2) return false;
2245  }
2246  return true;
2247 }
2248 
2249 bool C4PropertyDelegateGraph::IsPasteValid(const C4Value &val) const
2250 {
2251  // Unfortunately, there is no good way to determine the correct value for fixed structure / position graph pastes
2252  // So just reject pastes for now
2253  // (TODO: Could store a default structure in a property and compare to that)
2254  if (horizontal_fix || vertical_fix || structure_fix) return false;
2255  // Check storage as prop list
2256  const int32_t n_props = 2;
2257  C4Value prop_vals[n_props]; // vertices & edges
2258  C4PropertyName property_names[n_props] = { P_Vertices, P_Edges };
2259  C4PropList *val_proplist = val.getPropList();
2260  if (!val_proplist) return false;
2261  for (int32_t i = 0; i < n_props; ++i)
2262  {
2263  val_proplist->GetProperty(property_names[i], &prop_vals[i]);
2264  }
2265  // extra properties are OK
2266  // Check validity of vertices and edges
2267  return IsVertexPasteValid(prop_vals[0]) && IsEdgePasteValid(prop_vals[1]);
2268 }
2269 
2270 void C4PropertyDelegateGraph::ConnectSignals(C4ConsoleQtShape *shape, const C4PropertyPath &property_path) const
2271 {
2272  C4ConsoleQtGraph *shape_graph = static_cast<C4ConsoleQtGraph *>(shape);
2273  connect(shape_graph, &C4ConsoleQtGraph::GraphEdit, this, [this, shape, property_path](C4ControlEditGraph::Action action, int32_t index, int32_t x, int32_t y) {
2274  // Send graph editing via queue
2275  ::Control.DoInput(CID_EditGraph, new C4ControlEditGraph(property_path.GetGetPath(), action, index, x, y), CDT_Decide);
2276  // Also send update callback to root object
2277  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
2278  });
2279  connect(shape, &C4ConsoleQtShape::BorderSelectionChanged, this, []() {
2280  // Different part of the shape selected: Refresh info on next update
2282  });
2283 }
2284 
2285 
2286 
2287 /* Areas shown in viewport: Polyline */
2288 
2289 C4PropertyDelegatePolyline::C4PropertyDelegatePolyline(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2290  : C4PropertyDelegateGraph(factory, props)
2291 {
2292 }
2293 
2294 void C4PropertyDelegatePolyline::DoPaint(QPainter *painter, const QRect &inner_rect) const
2295 {
2296  // Draw symbol as a sequence of connected lines
2297  QPoint ctr = inner_rect.center();
2298  int r = inner_rect.height() * 7 / 20;
2299  painter->drawLine(ctr + QPoint(-r, +r), ctr + QPoint(-r/3, -r));
2300  painter->drawLine(ctr + QPoint(-r / 3, -r), ctr + QPoint(+r / 3, +r));
2301  painter->drawLine(ctr + QPoint(+r / 3, +r), ctr + QPoint(+r, -r));
2302 }
2303 
2304 bool C4PropertyDelegatePolyline::IsPasteValid(const C4Value &val) const
2305 {
2306  // Expect just a vertex array
2307  return IsVertexPasteValid(val);
2308 }
2309 
2310 
2311 /* Areas shown in viewport: Closed polyon */
2312 
2313 C4PropertyDelegatePolygon::C4PropertyDelegatePolygon(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2314  : C4PropertyDelegateGraph(factory, props)
2315 {
2316 }
2317 
2318 void C4PropertyDelegatePolygon::DoPaint(QPainter *painter, const QRect &inner_rect) const
2319 {
2320  // Draw symbol as a parallelogram
2321  QPoint ctr = inner_rect.center();
2322  int r = inner_rect.height() * 7 / 20;
2323  painter->drawLine(ctr + QPoint(-r * 3 / 2, +r), ctr + QPoint(-r, -r));
2324  painter->drawLine(ctr + QPoint(-r, -r), ctr + QPoint(+r * 3 / 2, -r));
2325  painter->drawLine(ctr + QPoint(+r * 3 / 2, -r), ctr + QPoint(+r, +r));
2326  painter->drawLine(ctr + QPoint(+r, +r), ctr + QPoint(-r * 3 / 2, +r));
2327 }
2328 
2329 bool C4PropertyDelegatePolygon::IsPasteValid(const C4Value &val) const
2330 {
2331  // Expect just a vertex array
2332  return IsVertexPasteValid(val);
2333 }
2334 
2335 
2336 /* Delegate factory: Create delegates based on the C4Value type */
2337 
2338 C4PropertyDelegateFactory::C4PropertyDelegateFactory() : current_editor(nullptr), property_model(nullptr), effect_delegate(this, nullptr)
2339 {
2340 
2341 }
2342 
2343 C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByPropList(C4PropList *props) const
2344 {
2345  if (props)
2346  {
2347  const C4String *str = props->GetPropertyStr(P_Type);
2348  if (str)
2349  {
2350  // create default base types
2351  if (str->GetData() == "int") return new C4PropertyDelegateInt(this, props);
2352  if (str->GetData() == "string") return new C4PropertyDelegateString(this, props);
2353  if (str->GetData() == "array") return new C4PropertyDelegateArray(this, props);
2354  if (str->GetData() == "proplist") return new C4PropertyDelegatePropList(this, props);
2355  if (str->GetData() == "color") return new C4PropertyDelegateColor(this, props);
2356  if (str->GetData() == "def") return new C4PropertyDelegateDef(this, props);
2357  if (str->GetData() == "object") return new C4PropertyDelegateObject(this, props);
2358  if (str->GetData() == "enum") return new C4PropertyDelegateEnum(this, props);
2359  if (str->GetData() == "sound") return new C4PropertyDelegateSound(this, props);
2360  if (str->GetData() == "bool") return new C4PropertyDelegateBool(this, props);
2361  if (str->GetData() == "has_effect") return new C4PropertyDelegateHasEffect(this, props);
2362  if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this, props);
2363  if (str->GetData() == "rect") return new C4PropertyDelegateRect(this, props);
2364  if (str->GetData() == "circle") return new C4PropertyDelegateCircle(this, props);
2365  if (str->GetData() == "point") return new C4PropertyDelegatePoint(this, props);
2366  if (str->GetData() == "graph") return new C4PropertyDelegateGraph(this, props);
2367  if (str->GetData() == "polyline") return new C4PropertyDelegatePolyline(this, props);
2368  if (str->GetData() == "polygon") return new C4PropertyDelegatePolygon(this, props);
2369  if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this, props);
2370  // unknown type
2371  LogF("Invalid delegate type: %s.", str->GetCStr());
2372  }
2373  }
2374  // Default fallback
2375  return new C4PropertyDelegateC4ValueInput(this, props);
2376 }
2377 
2378 C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByValue(const C4Value &val) const
2379 {
2380  auto iter = delegates.find(val.getPropList());
2381  if (iter != delegates.end()) return iter->second.get();
2382  C4PropertyDelegate *new_delegate = CreateDelegateByPropList(val.getPropList());
2383  delegates.insert(std::make_pair(val.getPropList(), std::unique_ptr<C4PropertyDelegate>(new_delegate)));
2384  return new_delegate;
2385 }
2386 
2387 C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByIndex(const QModelIndex &index) const
2388 {
2389  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2390  if (!prop) return nullptr;
2391  if (!prop->delegate) prop->delegate = GetDelegateByValue(prop->delegate_info);
2392  return prop->delegate;
2393 }
2394 
2395 void C4PropertyDelegateFactory::ClearDelegates()
2396 {
2397  delegates.clear();
2398 }
2399 
2400 void C4PropertyDelegateFactory::EditorValueChanged(QWidget *editor)
2401 {
2402  emit commitData(editor);
2403 }
2404 
2405 void C4PropertyDelegateFactory::EditingDone(QWidget *editor)
2406 {
2407  emit commitData(editor);
2408  //emit closeEditor(editor); - done by qt somewhere else...
2409 }
2410 
2411 void C4PropertyDelegateFactory::setEditorData(QWidget *editor, const QModelIndex &index) const
2412 {
2413  // Put property value from proplist into editor
2414  C4PropertyDelegate *d = GetDelegateByIndex(index);
2415  if (!CheckCurrentEditor(d, editor)) return;
2416  // Fetch property only first time - ignore further updates to the same value to simplify editing
2417  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2418  if (!prop) return;
2419  C4Value val;
2420  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2421  if (!prop->about_to_edit && val == last_edited_value) return;
2422  last_edited_value = val;
2423  prop->about_to_edit = false;
2424  d->SetEditorData(editor, val, d->GetPathForProperty(prop));
2425 }
2426 
2427 void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
2428 {
2429  // Fetch property value from editor and set it into proplist
2430  C4PropertyDelegate *d = GetDelegateByIndex(index);
2431  if (!CheckCurrentEditor(d, editor)) return;
2432  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2433  SetPropertyData(d, editor, prop);
2434 }
2435 
2436 void C4PropertyDelegateFactory::SetPropertyData(const C4PropertyDelegate *d, QObject *editor, C4ConsoleQtPropListModel::Property *editor_prop) const
2437 {
2438  // Set according to delegate
2439  const C4PropertyPath set_path = d->GetPathForProperty(editor_prop);
2440  d->SetModelData(editor, set_path, editor_prop->shape ? editor_prop->shape->Get() : nullptr);
2441 }
2442 
2443 QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
2444 {
2445  C4PropertyDelegate *d = GetDelegateByIndex(index);
2446  if (!d) return nullptr;
2447  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2448  prop->about_to_edit = true;
2449  QWidget *editor = d->CreateEditor(this, parent, option, true, false);
2450  // Connect value change signals (if editing is possible for this property)
2451  // For some reason, commitData needs a non-const pointer
2452  if (editor)
2453  {
2454  connect(d, &C4PropertyDelegate::EditorValueChangedSignal, editor, [editor, this](QWidget *signal_editor) {
2455  if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditorValueChanged(editor);
2456  });
2457  connect(d, &C4PropertyDelegate::EditingDoneSignal, editor, [editor, this](QWidget *signal_editor) {
2458  if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditingDone(editor);
2459  });
2460  }
2461  current_editor = editor;
2462  current_editor_delegate = d;
2463  return editor;
2464 }
2465 
2466 void C4PropertyDelegateFactory::destroyEditor(QWidget *editor, const QModelIndex &index) const
2467 {
2468  if (editor == current_editor)
2469  {
2470  current_editor = nullptr;
2471  current_editor_delegate = nullptr;
2473  }
2474  QStyledItemDelegate::destroyEditor(editor, index);
2475 }
2476 
2477 void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
2478 {
2479  C4PropertyDelegate *d = GetDelegateByIndex(index);
2480  if (!CheckCurrentEditor(d, editor)) return;
2481  return d->UpdateEditorGeometry(editor, option);
2482 }
2483 
2484 QSize C4PropertyDelegateFactory::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
2485 {
2486  int height = QApplication::fontMetrics().height() + 4;
2487  return QSize(100, height);
2488 }
2489 
2490 void C4PropertyDelegateFactory::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
2491 {
2492  // Delegate has custom painting?
2493  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2494  C4PropertyDelegate *d = GetDelegateByIndex(index);
2495  if (d && prop && d->HasCustomPaint())
2496  {
2497  C4Value val;
2498  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2499  if (d->Paint(painter, option, val)) return;
2500  }
2501  // Otherwise use default paint implementation
2502  QStyledItemDelegate::paint(painter, option, index);
2503 }
2504 
2505 void C4PropertyDelegateFactory::OnPropListChanged()
2506 {
2507  if (current_editor) emit closeEditor(current_editor);
2508 }
2509 
2510 bool C4PropertyDelegateFactory::CheckCurrentEditor(C4PropertyDelegate *d, QWidget *editor) const
2511 {
2512  if (!d || (editor && editor != current_editor) || d != current_editor_delegate)
2513  {
2514  //const_cast<C4PropertyDelegateFactory *>(this)->emit closeEditor(current_editor);
2515  destroyEditor(current_editor, QModelIndex());
2516  return false;
2517  }
2518  return true;
2519 }
2520 
2521 static const QString property_mime_type("application/OpenClonkProperty");
2522 
2523 void C4PropertyDelegateFactory::CopyToClipboard(const QModelIndex &index)
2524 {
2525  // Re-resolve property. May have shifted while the menu was open
2526  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2527  C4PropertyDelegate *d = GetDelegateByIndex(index);
2528  if (!prop || !d) return;
2529  // Get data to copy
2530  C4Value val;
2531  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2532  StdStrBuf data_str(val.GetDataString(99999));
2533  // Copy it as an internal mime type and text
2534  // Presence of the internal type shows that this is a copied property so it can be safely evaluate without sync problems
2535  QClipboard *clipboard = QApplication::clipboard();
2536  clipboard->clear();
2537  std::unique_ptr<QMimeData> data(new QMimeData());
2538  data->setData(property_mime_type, QByteArray(data_str.getData(), data_str.getSize()));
2539  data->setText(data_str.getData());
2540  clipboard->setMimeData(data.release());
2541 }
2542 
2543 bool C4PropertyDelegateFactory::PasteFromClipboard(const QModelIndex &index, bool check_only)
2544 {
2545  // Re-resolve property. May have shifted while the menu was open
2546  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2547  C4PropertyDelegate *d = GetDelegateByIndex(index);
2548  if (!prop || !d) return false;
2549  // Check value to paste
2550  QClipboard *clipboard = QApplication::clipboard();
2551  const QMimeData *data = clipboard->mimeData();
2552  if (!data) return false; // empty clipboard
2553  // Prefer copied property; fall back to text
2554  StdStrBuf str_data;
2555  if (data->hasFormat(property_mime_type))
2556  {
2557  QByteArray prop_data = data->data(property_mime_type);
2558  str_data.Copy(prop_data);
2559  // Check data type
2560  C4Value val = ::AulExec.DirectExec(&::ScriptEngine, str_data.getData(), "paste check", false, nullptr, false);
2561  if (!d->IsPasteValid(val)) return false;
2562  }
2563  else if (data->hasText())
2564  {
2565  // Text can always be pasted.
2566  // Cannot perform a type check here because a function may have been copied that affects sync.
2567  QString text = data->text();
2568  str_data.Copy(text.toUtf8());
2569  }
2570  else
2571  {
2572  // Unknown data type in clipboard. Cannot paste.
2573  return false;
2574  }
2575  if (check_only) return true;
2576  // Alright, paste!
2577  d->GetPathForProperty(prop).SetProperty(str_data.getData());
2578  return true;
2579 }
2580 
2581 bool C4PropertyDelegateFactory::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
2582 {
2583  // Custom context menu on item
2584  // I would like to use the regular context menu functions of Qt on the parent widget
2585  // but something is eating the right-click event before it triggers a context event.
2586  // So just hack it on right click.
2587  // Button check
2588  if (event->type() == QEvent::Type::MouseButtonPress)
2589  {
2590  QMouseEvent *mev = static_cast<QMouseEvent *>(event);
2591  if (mev->button() == Qt::MouseButton::RightButton)
2592  {
2593  // Item check
2594  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2595  C4PropertyDelegate *d = GetDelegateByIndex(index);
2596  if (d && prop)
2597  {
2598  // Context menu on a valid property: Show copy+paste menu
2599  QMenu *context = new QMenu(const_cast<QWidget *>(option.widget));
2600  QAction *copy_action = new QAction(LoadResStr("IDS_DLG_COPY"), context);
2601  QAction *paste_action = new QAction(LoadResStr("IDS_DLG_PASTE"), context);
2602  QModelIndex index_copy(index);
2603  connect(copy_action, &QAction::triggered, this, [this, index_copy]() {
2604  this->CopyToClipboard(index_copy);
2605  });
2606  connect(paste_action, &QAction::triggered, this, [this, index_copy]() {
2607  this->PasteFromClipboard(index_copy, false);
2608  });
2609  paste_action->setEnabled(PasteFromClipboard(index_copy, true)); // Paste grayed out if not valid
2610  context->addAction(copy_action);
2611  context->addAction(paste_action);
2612  context->popup(mev->globalPos());
2613  context->connect(context, &QMenu::aboutToHide, context, &QWidget::deleteLater);
2614  // It's easier to see which item is affected when it's selected
2615  QItemSelectionModel *sel_model = property_model->GetSelectionModel();
2616  QItemSelection new_sel;
2617  new_sel.select(model->index(index.row(), 0, index.parent()), index);
2618  sel_model->select(new_sel, QItemSelectionModel::SelectionFlag::SelectCurrent);
2619  return true;
2620  }
2621  }
2622  }
2623  return QStyledItemDelegate::editorEvent(event, model, option, index);
2624 }
2625 
2626 
2627 /* Proplist table view */
2628 
2629 C4ConsoleQtPropListModel::C4ConsoleQtPropListModel(C4PropertyDelegateFactory *delegate_factory)
2630  : delegate_factory(delegate_factory), selection_model(nullptr)
2631 {
2632  header_font.setBold(true);
2633  important_property_font.setBold(true);
2634  connect(this, &C4ConsoleQtPropListModel::ProplistChanged, this, &C4ConsoleQtPropListModel::UpdateSelection, Qt::QueuedConnection);
2635  layout_valid = false;
2636 }
2637 
2638 C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel()
2639 {
2640 }
2641 
2642 bool C4ConsoleQtPropListModel::AddPropertyGroup(C4PropList *add_proplist, int32_t group_index, QString name, C4PropList *target_proplist, const C4PropertyPath &group_target_path, C4Object *base_object, C4String *default_selection, int32_t *default_selection_index)
2643 {
2644  // Add all properties from this EditorProps group
2645  std::vector<C4String *> property_names = add_proplist->GetUnsortedProperties(nullptr);
2646  if (!property_names.size()) return false;
2647  // Prepare group array
2648  if (property_groups.size() == group_index)
2649  {
2650  layout_valid = false;
2651  property_groups.resize(group_index + 1);
2652  }
2653  PropertyGroup &properties = property_groups[group_index];
2654  C4PropListStatic *proplist_static = add_proplist->IsStatic();
2655  // Resolve properties
2656  struct PropAndKey
2657  {
2658  C4PropList *prop;
2659  C4String *key;
2660  int32_t priority;
2661  C4String *name;
2662 
2663  PropAndKey(C4PropList *prop, C4String *key, int32_t priority, C4String *name)
2664  : prop(prop), key(key), priority(priority), name(name) {}
2665  };
2666  std::vector<PropAndKey> new_properties_resolved;
2667  new_properties_resolved.reserve(property_names.size());
2668  for (C4String *prop_name : property_names)
2669  {
2670  C4Value prop_val;
2671  add_proplist->GetPropertyByS(prop_name, &prop_val);
2672  C4PropList *prop = prop_val.getPropList();
2673  if (prop)
2674  {
2675  C4String *name = prop->GetPropertyStr(P_Name);
2676  if (!name) name = prop_name;
2677  int32_t priority = prop->GetPropertyInt(P_Priority);
2678  new_properties_resolved.emplace_back(PropAndKey({ prop, prop_name, priority, name }));
2679  }
2680  }
2681  // Sort by priority primarily and name secondarily
2682  std::sort(new_properties_resolved.begin(), new_properties_resolved.end(), [](const PropAndKey &a, const PropAndKey &b) -> bool {
2683  if (a.priority != b.priority) return a.priority > b.priority;
2684  return strcmp(a.name->GetCStr(), b.name->GetCStr()) < 0;
2685  });
2686  // Setup group
2687  properties.name = name;
2688  if (properties.props.size() != new_properties_resolved.size())
2689  {
2690  layout_valid = false;
2691  properties.props.resize(new_properties_resolved.size());
2692  }
2693  C4Effect *fx = target_proplist->GetEffect();
2694  for (int32_t i = 0; i < new_properties_resolved.size(); ++i)
2695  {
2696  Property *prop = &properties.props[i];
2697  // Property access path
2698  prop->parent_value.SetPropList(target_proplist);
2699  prop->property_path = group_target_path;
2700  // ID for default selection memory
2701  const PropAndKey &prop_def = new_properties_resolved[i];
2702  if (default_selection == prop_def.key) *default_selection_index = i;
2703  // Property data
2704  prop->help_text = nullptr;
2705  prop->delegate_info.Set0(); // default C4Value delegate
2706  prop->group_idx = group_index;
2707  prop->about_to_edit = false;
2708  prop->key = prop_def.prop->GetPropertyStr(P_Key);
2709  if (!prop->key) properties.props[i].key = prop_def.key;
2710  prop->display_name = prop_def.name;
2711  if (!prop->display_name) prop->display_name = prop_def.key;
2712  prop->help_text = prop_def.prop->GetPropertyStr(P_EditorHelp);
2713  prop->priority = prop_def.priority;
2714  prop->delegate_info.SetPropList(prop_def.prop);
2715  prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
2716  C4Value v;
2717  C4Value v_target_proplist = C4VPropList(target_proplist);
2718  prop->delegate->GetPropertyValue(v_target_proplist, prop->key, 0, &v);
2719  // Connect editable shape to property
2720  C4PropertyPath new_shape_property_path = prop->delegate->GetPathForProperty(prop);
2721  const C4PropertyDelegateShape *new_shape_delegate = prop->delegate->GetShapeDelegate(v, &new_shape_property_path);
2722  if (new_shape_delegate != prop->shape_delegate || !(prop->shape_property_path == new_shape_property_path))
2723  {
2724  prop->shape_delegate = new_shape_delegate;
2725  prop->shape_property_path = new_shape_property_path;
2726  if (new_shape_delegate)
2727  {
2728  // Re-use loaded shape if possible (e.g. if only the index has moved)
2729  std::string shape_index = std::string(prop->shape_property_path.GetGetPath());
2730  prop->shape = &shapes[shape_index];
2731  C4ConsoleQtShape *shape = prop->shape->Get();
2732  if (shape)
2733  {
2734  if (shape->GetProperties() != new_shape_delegate->GetCreationProps().getPropList())
2735  {
2736  // Shape at same path but with different properties? Then re-create
2737  shape = nullptr;
2738  }
2739  }
2740  if (!shape)
2741  {
2742  // New shape or shape type mismatch: Generate new shape at this path and put into the shape holder list
2743  shape = ::Console.EditCursor.GetShapes()->CreateShape(base_object ? base_object : target_proplist->GetObject(), new_shape_delegate->GetCreationProps().getPropList(), v, new_shape_delegate);
2744  C4PropertyDelegateFactory *factory = this->delegate_factory;
2745  new_shape_delegate->ConnectSignals(shape, prop->shape_property_path);
2746  prop->shape->Set(shape);
2747  prop->shape->SetLastValue(v);
2748  }
2749  }
2750  else
2751  {
2752  prop->shape = nullptr;
2753  }
2754  }
2755  if (prop->shape)
2756  {
2757  // Mark this shape to be kept aftre update is complete
2758  prop->shape->visit();
2759  // Update shape by value if it was changed externally
2760  if (!prop->shape->GetLastValue().IsIdenticalTo(v))
2761  {
2762  prop->shape->Get()->SetValue(v);
2763  prop->shape->SetLastValue(v);
2764  }
2765  }
2766  }
2767  return true;
2768 }
2769 
2770 bool C4ConsoleQtPropListModel::AddEffectGroup(int32_t group_index, C4Object *base_object)
2771 {
2772  // Count non-dead effects
2773  C4Effect **effect_list = base_object ? &base_object->pEffects : &::ScriptEngine.pGlobalEffects;
2774  int32_t num_effects = 0;
2775  for (C4Effect *effect = *effect_list; effect; effect = effect->pNext)
2776  {
2777  num_effects += effect->IsActive();
2778  }
2779  // Return false to signal that no effect group has been added
2780  if (!num_effects) return false;
2781  // Prepare group array
2782  if (property_groups.size() == group_index)
2783  {
2784  layout_valid = false;
2785  property_groups.resize(group_index + 1);
2786  }
2787  PropertyGroup &properties = property_groups[group_index];
2788  if (properties.props.size() != num_effects)
2789  {
2790  layout_valid = false;
2791  properties.props.resize(num_effects);
2792  }
2793  properties.name = LoadResStr("IDS_CNS_EFFECTS");
2794  // Add all (non-dead) effects of given object (or global effects if base_object is nullptr)
2795  int32_t num_added = 0;
2796  for (C4Effect *effect = *effect_list; effect; effect = effect->pNext)
2797  {
2798  if (effect->IsActive())
2799  {
2800  Property *prop = &properties.props[num_added++];
2801  prop->parent_value.SetPropList(base_object ? (C4PropList *) base_object : &::ScriptEngine);
2802  prop->property_path = C4PropertyPath(effect, base_object);
2803  prop->help_text = nullptr;
2804  prop->delegate_info.Set0();
2805  prop->group_idx = group_index;
2806  prop->key = ::Strings.RegString(prop->property_path.GetGetPath());
2807  prop->display_name = effect->GetPropertyStr(P_Name);
2808  prop->priority = 0;
2809  prop->delegate = delegate_factory->GetEffectDelegate();
2810  prop->shape = nullptr;
2811  prop->shape_delegate = nullptr;
2812  prop->shape_property_path.Clear();
2813  prop->about_to_edit = false;
2814  prop->group_idx = group_index;
2815  }
2816  }
2817  // Return true to signal that effect group has been added
2818  return true;
2819 }
2820 
2821 void C4ConsoleQtPropListModel::SetBasePropList(C4PropList *new_proplist)
2822 {
2823  // Clear stack and select new proplist
2824  // Update properties
2825  target_value.SetPropList(new_proplist);
2826  base_proplist.SetPropList(new_proplist);
2827  // objects derive their custom properties
2828  info_proplist.SetPropList(target_value.getObj());
2829  target_path = C4PropertyPath(new_proplist);
2830  target_path_stack.clear();
2831  UpdateValue(true);
2832  delegate_factory->OnPropListChanged();
2833 }
2834 
2835 void C4ConsoleQtPropListModel::DescendPath(const C4Value &new_value, C4PropList *new_info_proplist, const C4PropertyPath &new_path)
2836 {
2837  // Add previous proplist to stack
2838  target_path_stack.push_back(TargetStackEntry(target_path, target_value, info_proplist));
2839  // descend
2840  target_value = new_value;
2841  info_proplist.SetPropList(new_info_proplist);
2842  target_path = new_path;
2843  UpdateValue(true);
2844  delegate_factory->OnPropListChanged();
2845 }
2846 
2847 void C4ConsoleQtPropListModel::AscendPath()
2848 {
2849  // Go up in target stack (if possible)
2850  for (;;)
2851  {
2852  if (!target_path_stack.size())
2853  {
2854  SetBasePropList(nullptr);
2855  return;
2856  }
2857  TargetStackEntry entry = target_path_stack.back();
2858  target_path_stack.pop_back();
2859  if (!entry.value || !entry.info_proplist) continue; // property was removed; go up further in stack
2860  // Safety: Make sure we're still on the same value
2861  C4Value target = entry.path.ResolveValue();
2862  if (!target.IsIdenticalTo(entry.value)) continue;
2863  // Set new value
2864  target_path = entry.path;
2865  target_value = entry.value;
2866  info_proplist = entry.info_proplist;
2867  UpdateValue(true);
2868  break;
2869  }
2870  // Any current editor needs to close
2871  delegate_factory->OnPropListChanged();
2872 }
2873 
2874 void C4ConsoleQtPropListModel::UpdateValue(bool select_default)
2875 {
2876  emit layoutAboutToBeChanged();
2877  // Update target value from path
2878  target_value = target_path.ResolveValue();
2879  // Prepare shape list update
2880  C4ConsoleQtShapeHolder::begin_visit();
2881  // Safe-get from C4Values in case any prop lists or arrays got deleted
2882  int32_t num_groups, default_selection_group = -1, default_selection_index = -1;
2883  switch (target_value.GetType())
2884  {
2885  case C4V_PropList:
2886  num_groups = UpdateValuePropList(target_value._getPropList(), &default_selection_group, &default_selection_index);
2887  break;
2888  case C4V_Array:
2889  num_groups = UpdateValueArray(target_value._getArray(), &default_selection_group, &default_selection_index);
2890  break;
2891  default: // probably nil
2892  num_groups = 0;
2893  break;
2894  }
2895  // Remove any unreferenced shapes
2896  for (auto iter = shapes.begin(); iter != shapes.end(); )
2897  {
2898  if (!iter->second.was_visited())
2899  {
2900  iter = shapes.erase(iter);
2901  }
2902  else
2903  {
2904  ++iter;
2905  }
2906  }
2907  // Update model range
2908  if (num_groups != property_groups.size())
2909  {
2910  layout_valid = false;
2911  property_groups.resize(num_groups);
2912  }
2913  if (!layout_valid)
2914  {
2915  // We do not adjust persistent indices for now
2916  // Usually, if layout changed, it's because the target value changed and we don't want to select/expand random stuff in the new proplist
2917  layout_valid = true;
2918  }
2919  emit layoutChanged();
2920  QModelIndex topLeft = index(0, 0, QModelIndex());
2921  QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1, QModelIndex());
2922  emit dataChanged(topLeft, bottomRight);
2923  // Initial selection
2924  if (select_default) emit ProplistChanged(default_selection_group, default_selection_index);
2925 }
2926 
2927 void C4ConsoleQtPropListModel::UpdateSelection(int32_t major_sel, int32_t minor_sel) const
2928 {
2929  if (selection_model)
2930  {
2931  // Select by indexed elements only
2932  selection_model->clearSelection();
2933  if (major_sel >= 0)
2934  {
2935  QModelIndex sel = index(major_sel, 0, QModelIndex());
2936  if (minor_sel >= 0) sel = index(minor_sel, 0, sel);
2937  selection_model->select(sel, QItemSelectionModel::SelectCurrent);
2938  }
2939  else
2940  {
2941  selection_model->select(QModelIndex(), QItemSelectionModel::SelectCurrent);
2942  }
2943  }
2944 }
2945 
2946 int32_t C4ConsoleQtPropListModel::UpdateValuePropList(C4PropList *target_proplist, int32_t *default_selection_group, int32_t *default_selection_index)
2947 {
2948  assert(target_proplist);
2949  C4PropList *base_proplist = this->base_proplist.getPropList();
2950  C4Object *base_obj = this->base_proplist.getObj(), *obj = nullptr;
2951  C4PropList *info_proplist = this->info_proplist.getPropList();
2952  int32_t num_groups = 0;
2953  // Selected shape properties
2954  C4ConsoleQtShape *selected_shape = ::Console.EditCursor.GetShapes()->GetSelectedShape();
2955  if (selected_shape)
2956  {
2957  // Find property information for this shape
2958  // Could also remember this pointer for every shape holder
2959  // - but that would have to be updated on any property group vector resize
2960  Property *prop = nullptr;
2961  for (PropertyGroup &grp : property_groups)
2962  {
2963  for (Property &check_prop : grp.props)
2964  {
2965  if (check_prop.shape && check_prop.shape->Get() == selected_shape)
2966  {
2967  prop = &check_prop;
2968  break;
2969  }
2970  }
2971  if (prop) break;
2972  }
2973  // Update selected shape item information
2974  if (prop && prop->delegate)
2975  {
2976  C4PropList *shape_item_editorprops, *shape_item_value;
2977  C4String *shape_item_name = nullptr;
2978  C4PropertyPath shape_item_target_path;
2979  C4Value v;
2980  C4Value v_target_proplist = C4VPropList(target_proplist);
2981  prop->delegate->GetPropertyValue(v_target_proplist, prop->key, 0, &v);
2982  C4PropertyPath shape_property_path = prop->delegate->GetPathForProperty(prop);
2983  const C4PropertyDelegateShape *current_shape_delegate = prop->delegate->GetShapeDelegate(v, &shape_property_path); // to resolve v
2984  if (::Console.EditCursor.GetShapes()->GetSelectedShapeData(v, prop->shape_property_path, &shape_item_editorprops, &shape_item_value, &shape_item_name, &shape_item_target_path))
2985  {
2986  if (AddPropertyGroup(shape_item_editorprops, num_groups, QString(shape_item_name ? shape_item_name->GetCStr() :"???"), shape_item_value, shape_item_target_path, obj, nullptr, nullptr))
2987  {
2988  ++num_groups;
2989  }
2990  }
2991  }
2992  }
2993  // Published properties
2994  if (info_proplist)
2995  {
2996  C4String *default_selection = info_proplist->GetPropertyStr(P_DefaultEditorProp);
2997  obj = info_proplist->GetObject();
2998  // Properties from effects (no inheritance supported)
2999  if (obj)
3000  {
3001  for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
3002  {
3003  if (!fx->IsActive()) continue; // skip dead effects
3004  QString name = fx->GetName();
3005  C4PropList *effect_editorprops = fx->GetPropertyPropList(P_EditorProps);
3006  if (effect_editorprops && AddPropertyGroup(effect_editorprops, num_groups, name, fx, C4PropertyPath(fx, obj), obj, nullptr, nullptr))
3007  ++num_groups;
3008  }
3009  }
3010  // Properties from object (but not on definition)
3011  if (obj || !info_proplist->GetDef())
3012  {
3013  C4PropList *info_editorprops = info_proplist->GetPropertyPropList(P_EditorProps);
3014  if (info_editorprops)
3015  {
3016  QString name = info_proplist->GetName();
3017  if (AddPropertyGroup(info_editorprops, num_groups, name, target_proplist, target_path, base_obj, default_selection, default_selection_index))
3018  ++num_groups;
3019  // Assign group for default selection
3020  if (*default_selection_index >= 0)
3021  {
3022  *default_selection_group = num_groups - 1;
3023  default_selection = nullptr; // don't find any other instances
3024  }
3025  }
3026  }
3027  // properties from global list for objects
3028  if (obj)
3029  {
3030  C4Def *editor_base = C4Id2Def(C4ID::EditorBase);
3031  C4PropList *info_editorprops = nullptr;
3032 
3033  if (editor_base && (info_editorprops = editor_base->GetPropertyPropList(P_EditorProps)))
3034  {
3035  if (AddPropertyGroup(info_editorprops, num_groups, LoadResStr("IDS_CNS_OBJECT"), target_proplist, target_path, base_obj, nullptr, nullptr))
3036  ++num_groups;
3037  }
3038  }
3039  }
3040  // Always: Internal properties
3041  auto new_properties = target_proplist->GetSortedLocalProperties();
3042  if (property_groups.size() == num_groups) property_groups.resize(num_groups + 1);
3043  PropertyGroup &internal_properties = property_groups[num_groups];
3044  internal_properties.name = LoadResStr("IDS_CNS_INTERNAL");
3045  internal_properties.props.resize(new_properties.size());
3046  for (int32_t i = 0; i < new_properties.size(); ++i)
3047  {
3048  internal_properties.props[i].parent_value = this->target_value;
3049  internal_properties.props[i].property_path = target_path;
3050  internal_properties.props[i].key = new_properties[i];
3051  internal_properties.props[i].display_name = new_properties[i];
3052  internal_properties.props[i].help_text = nullptr;
3053  internal_properties.props[i].priority = 0;
3054  internal_properties.props[i].delegate_info.Set0(); // default C4Value delegate
3055  internal_properties.props[i].delegate = nullptr; // init when needed
3056  internal_properties.props[i].group_idx = num_groups;
3057  internal_properties.props[i].shape = nullptr;
3058  internal_properties.props[i].shape_property_path.Clear();
3059  internal_properties.props[i].shape_delegate = nullptr;
3060  internal_properties.props[i].about_to_edit = false;
3061  }
3062  ++num_groups;
3063  // Effects
3064  // Add after internal because the gorup may be added/removed quickly
3065  if (obj)
3066  {
3067  // Object: Show object effects
3068  if (AddEffectGroup(num_groups, obj))
3069  {
3070  ++num_groups;
3071  }
3072  }
3073  else if (target_proplist == &::ScriptEngine)
3074  {
3075  // Global object: Show global effects
3076  if (AddEffectGroup(num_groups, nullptr))
3077  {
3078  ++num_groups;
3079  }
3080  }
3081  return num_groups;
3082 }
3083 
3084 int32_t C4ConsoleQtPropListModel::UpdateValueArray(C4ValueArray *target_array, int32_t *default_selection_group, int32_t *default_selection_index)
3085 {
3086  if (property_groups.empty())
3087  {
3088  layout_valid = false;
3089  property_groups.resize(1);
3090  }
3091  C4PropList *info_proplist = this->info_proplist.getPropList();
3092  C4Value elements_delegate_value;
3093  if (info_proplist) info_proplist->GetProperty(P_Elements, &elements_delegate_value);
3094  property_groups[0].name = (info_proplist ? info_proplist->GetName() : LoadResStr("IDS_CNS_ARRAYEDIT"));
3095  PropertyGroup &properties = property_groups[0];
3096  if (properties.props.size() != target_array->GetSize())
3097  {
3098  layout_valid = false;
3099  properties.props.resize(target_array->GetSize());
3100  }
3101  C4PropertyDelegate *item_delegate = delegate_factory->GetDelegateByValue(elements_delegate_value);
3102  for (int32_t i = 0; i < properties.props.size(); ++i)
3103  {
3104  Property &prop = properties.props[i];
3105  prop.property_path = C4PropertyPath(target_path, i);
3106  prop.parent_value = target_value;
3107  prop.display_name = ::Strings.RegString(FormatString("%d", (int)i).getData());
3108  prop.help_text = nullptr;
3109  prop.key = nullptr;
3110  prop.priority = 0;
3111  prop.delegate_info = elements_delegate_value;
3112  prop.delegate = item_delegate;
3113  prop.about_to_edit = false;
3114  prop.group_idx = 0;
3115  prop.shape = nullptr; // array elements cannot have shapes
3116  prop.shape_property_path.Clear();
3117  prop.shape_delegate = nullptr;
3118  }
3119  return 1; // one group for the values
3120 }
3121 
3122 void C4ConsoleQtPropListModel::DoOnUpdateCall(const C4PropertyPath &updated_path, const C4PropertyDelegate *delegate)
3123 {
3124  // If delegate has its own update clalback, perform that on the root
3125  const char *update_callback = delegate->GetUpdateCallback();
3126  if (update_callback)
3127  {
3128  ::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(FormatString("%s->%s(%s)", updated_path.GetRoot(), update_callback, updated_path.GetGetPath()).getData(), 0, false));
3129  }
3130  // Do general object property update control
3131  C4PropList *base_proplist = this->base_proplist.getPropList();
3132  C4Value q;
3133  if (base_proplist && base_proplist->GetProperty(P_EditorPropertyChanged, &q))
3134  {
3135  ::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(FormatString("%s->%s(\"%s\")", updated_path.GetRoot(), ::Strings.P[P_EditorPropertyChanged].GetCStr(), updated_path.GetGetPath()).getData(), 0, false));
3136  }
3137 }
3138 
3139 C4ConsoleQtPropListModel::Property *C4ConsoleQtPropListModel::GetPropByIndex(const QModelIndex &index) const
3140 {
3141  if (!index.isValid()) return nullptr;
3142  // Resolve group and row
3143  int32_t group_index = index.internalId(), row = index.row();
3144  // Prop list access: Properties are on 2nd level
3145  if (!group_index) return nullptr;
3146  --group_index;
3147  if (group_index >= property_groups.size()) return nullptr;
3148  if (row < 0 || row >= property_groups[group_index].props.size()) return nullptr;
3149  return const_cast<Property *>(&property_groups[group_index].props[row]);
3150 }
3151 
3152 int C4ConsoleQtPropListModel::rowCount(const QModelIndex & parent) const
3153 {
3154  QModelIndex grandparent;
3155  // Top level: Property groups
3156  if (!parent.isValid())
3157  {
3158  return property_groups.size();
3159  }
3160  // Mid level: Descend into property lists
3161  grandparent = parent.parent();
3162  if (!grandparent.isValid())
3163  {
3164  if (parent.row() >= 0 && parent.row() < property_groups.size())
3165  return property_groups[parent.row()].props.size();
3166  }
3167  return 0; // no 3rd level depth
3168 }
3169 
3170 int C4ConsoleQtPropListModel::columnCount(const QModelIndex & parent) const
3171 {
3172  return 2; // Name + Data (or Index + Data)
3173 }
3174 
3175 QVariant C4ConsoleQtPropListModel::headerData(int section, Qt::Orientation orientation, int role) const
3176 {
3177  // Table headers
3178  if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal)
3179  {
3180  if (section == 0)
3181  if (target_value.GetType() == C4V_Array)
3182  return QVariant(LoadResStr("IDS_CNS_INDEXSHORT"));
3183  else
3184  return QVariant(LoadResStr("IDS_CTL_NAME"));
3185  if (section == 1) return QVariant(LoadResStr("IDS_CNS_VALUE"));
3186  }
3187  return QVariant();
3188 }
3189 
3190 QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) const
3191 {
3192  // Headers
3193  int32_t group_index = index.internalId();
3194  if (!group_index)
3195  {
3196  if (!index.column())
3197  {
3198  if (role == Qt::DisplayRole)
3199  {
3200  if (index.row() >= 0 && index.row() < property_groups.size())
3201  return property_groups[index.row()].name;
3202  }
3203  else if (role == Qt::FontRole)
3204  {
3205  return header_font;
3206  }
3207  }
3208  return QVariant();
3209  }
3210  // Query latest data from prop list
3211  Property *prop = GetPropByIndex(index);
3212  if (!prop) return QVariant();
3213  if (!prop->delegate) prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
3214  if (role == Qt::DisplayRole)
3215  {
3216  switch (index.column())
3217  {
3218  case 0: // First col: Property Name
3219  return QVariant(prop->display_name->GetCStr());
3220  case 1: // Second col: Property value
3221  {
3222  C4Value v;
3223  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3224  return QVariant(prop->delegate->GetDisplayString(v, target_value.getObj(), true));
3225  }
3226  }
3227  }
3228  else if (role == Qt::BackgroundColorRole && index.column()==1)
3229  {
3230  C4Value v;
3231  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3232  QColor bgclr = prop->delegate->GetDisplayBackgroundColor(v, target_value.getObj());
3233  if (bgclr.isValid()) return bgclr;
3234  }
3235  else if (role == Qt::TextColorRole && index.column() == 1)
3236  {
3237  C4Value v;
3238  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3239  QColor txtclr = prop->delegate->GetDisplayTextColor(v, target_value.getObj());
3240  if (txtclr.isValid()) return txtclr;
3241  }
3242  else if (role == Qt::DecorationRole && index.column() == 0 && prop->help_text && Config.Developer.ShowHelp)
3243  {
3244  // Help icons in left column
3245  return QIcon(":/editor/res/Help.png");
3246  }
3247  else if (role == Qt::FontRole && index.column() == 0)
3248  {
3249  if (prop->priority >= 100) return important_property_font;
3250  }
3251  else if (role == Qt::ToolTipRole && index.column() == 0)
3252  {
3253  // Tooltip from property description. Default to display name in case it got truncated.
3254  if (prop->help_text)
3255  return QString(prop->help_text->GetCStr());
3256  else
3257  return QString(prop->display_name->GetCStr());
3258  }
3259  // Nothing to show
3260  return QVariant();
3261 }
3262 
3263 QModelIndex C4ConsoleQtPropListModel::index(int row, int column, const QModelIndex &parent) const
3264 {
3265  if (column < 0 || column > 1) return QModelIndex();
3266  // Top level index?
3267  if (!parent.isValid())
3268  {
3269  // Top level has headers only
3270  if (row < 0 || row >= property_groups.size()) return QModelIndex();
3271  return createIndex(row, column, (quintptr)0u);
3272  }
3273  if (parent.internalId()) return QModelIndex(); // No 3rd level depth
3274  // Validate range of property
3275  const PropertyGroup *property_group = nullptr;
3276  if (parent.row() >= 0 && parent.row() < property_groups.size())
3277  {
3278  property_group = &property_groups[parent.row()];
3279  if (row < 0 || row >= property_group->props.size()) return QModelIndex();
3280  return createIndex(row, column, (quintptr)parent.row()+1);
3281  }
3282  return QModelIndex();
3283 }
3284 
3285 QModelIndex C4ConsoleQtPropListModel::parent(const QModelIndex &index) const
3286 {
3287  // Parent: Stored in internal ID
3288  auto parent_idx = index.internalId();
3289  if (parent_idx) return createIndex(parent_idx - 1, 0, (quintptr)0u);
3290  return QModelIndex();
3291 }
3292 
3293 Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const
3294 {
3295  Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;
3296  Property *prop = GetPropByIndex(index);
3297  if (index.isValid() && prop)
3298  {
3299  flags &= ~Qt::ItemIsDropEnabled; // only drop between the lines
3300  if (index.column() == 0)
3301  {
3302  // array elements can be re-arranged
3303  if (prop->parent_value.GetType() == C4V_Array) flags |= Qt::ItemIsDragEnabled;
3304  }
3305  else if (index.column() == 1)
3306  {
3307  // Disallow editing on readonly target (e.g. frozen proplist).
3308  // But always allow editing of effects.
3309  bool readonly = IsTargetReadonly() && prop->delegate != delegate_factory->GetEffectDelegate();
3310  if (!readonly)
3311  flags |= Qt::ItemIsEditable;
3312  else
3313  flags &= ~Qt::ItemIsEnabled;
3314  }
3315  }
3316  return flags;
3317 }
3318 
3319 Qt::DropActions C4ConsoleQtPropListModel::supportedDropActions() const
3320 {
3321  return Qt::MoveAction;
3322 }
3323 
3324 bool C4ConsoleQtPropListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
3325 {
3326  // Drag+Drop movement on array only
3327  if (action != Qt::MoveAction) return false;
3328  C4ValueArray *arr = target_value.getArray();
3329  if (!arr) return false;
3330  if (!data->hasFormat("application/vnd.text")) return false;
3331  if (row < 0) return false; // outside range: Could be above or below. Better don't drag at all.
3332  if (!parent.isValid()) return false; // in array only
3333  // Decode indices of rows to move
3334  QByteArray encodedData = data->data("application/vnd.text");
3335  StdStrBuf rearrange_call;
3336  rearrange_call.Format("MoveArrayItems(%%s, [%s], %d)", encodedData.constData(), row);
3337  target_path.DoCall(rearrange_call.getData());
3338  return true;
3339 }
3340 
3341 QStringList C4ConsoleQtPropListModel::mimeTypes() const
3342 {
3343  QStringList types;
3344  types << "application/vnd.text";
3345  return types;
3346 }
3347 
3348 QMimeData *C4ConsoleQtPropListModel::mimeData(const QModelIndexList &indexes) const
3349 {
3350  // Add all moved indexes
3351  QMimeData *mimeData = new QMimeData();
3352  QByteArray encodedData;
3353  int32_t count = 0;
3354  for (const QModelIndex &index : indexes)
3355  {
3356  if (index.isValid() && index.internalId())
3357  {
3358  if (count) encodedData.append(",");
3359  encodedData.append(QString::number(index.row()));
3360  ++count;
3361  }
3362  }
3363  mimeData->setData("application/vnd.text", encodedData);
3364  return mimeData;
3365 }
3366 
3367 QString C4ConsoleQtPropListModel::GetTargetPathHelp() const
3368 {
3369  // Help text in EditorInfo prop. Fall back to description.
3370  C4PropList *info_proplist = this->info_proplist.getPropList();
3371  if (!info_proplist) return QString();
3372  C4String *desc = info_proplist->GetPropertyStr(P_EditorHelp);
3373  if (!desc) desc = info_proplist->GetPropertyStr(P_Description);
3374  if (!desc) return QString();
3375  QString result = QString(desc->GetCStr());
3376  result = result.replace('|', '\n');
3377  return result;
3378 }
3379 
3380 const char *C4ConsoleQtPropListModel::GetTargetPathName() const
3381 {
3382  // Name prop of current info.
3383  C4PropList *info_proplist = this->info_proplist.getPropList();
3384  if (!info_proplist) return nullptr;
3385  C4String *name = info_proplist->GetPropertyStr(P_Name);
3386  return name ? name->GetCStr() : nullptr;
3387 }
3388 
3389 void C4ConsoleQtPropListModel::AddArrayElement()
3390 {
3391  C4Value new_val;
3392  C4PropList *info_proplist = this->info_proplist.getPropList();
3393  C4PropListStatic *info_proplist_static = nullptr;
3394  if (info_proplist)
3395  {
3396  info_proplist->GetProperty(P_DefaultValue, &new_val);
3397  info_proplist_static = info_proplist->IsStatic();
3398  }
3399  target_path.DoCall(FormatString("PushBack(%%s, %s)", new_val.GetDataString(10, info_proplist_static).getData()).getData());
3400 }
3401 
3402 void C4ConsoleQtPropListModel::RemoveArrayElement()
3403 {
3404  // Compose script command to remove all selected array indices
3405  StdStrBuf script;
3406  for (QModelIndex idx : selection_model->selectedIndexes())
3407  if (idx.isValid() && idx.column() == 0)
3408  if (script.getLength())
3409  script.AppendFormat(",%d", idx.row());
3410  else
3411  script.AppendFormat("%d", idx.row());
3412  if (script.getLength()) target_path.DoCall(FormatString("RemoveArrayIndices(%%s, [%s])", script.getData()).getData());
3413 }
3414 
3415 bool C4ConsoleQtPropListModel::IsTargetReadonly() const
3416 {
3417  if (target_path.IsEmpty()) return true;
3418  switch (target_value.GetType())
3419  {
3420  case C4V_Array:
3421  // Arrays are never frozen
3422  return false;
3423  case C4V_PropList:
3424  {
3425  C4PropList *parent_proplist = target_value._getPropList();
3426  if (parent_proplist->IsFrozen()) return true;
3427  return false;
3428  }
3429  default:
3430  return true;
3431  }
3432 }
3433 
3434 class C4ConsoleQtShape *C4ConsoleQtPropListModel::GetShapeByPropertyPath(const char *property_path)
3435 {
3436  // Lookup in map
3437  auto entry = shapes.find(std::string(property_path));
3438  if (entry == shapes.end()) return nullptr;
3439  return entry->second.Get();
3440 }
C4Effect * pNext
Definition: C4Effect.h:75
const char * getData() const
Definition: StdBuf.h:450
C4SoundSystem SoundSystem
Definition: C4Application.h:42
C4EditCursor EditCursor
Definition: C4Console.h:90
int32_t GetY() const
Definition: C4Object.h:287
C4String P[P_LAST]
StdStrBuf GetData() const
Definition: C4StringTable.h:50
C4V_Type
Definition: C4Value.h:23
C4Config Config
Definition: C4Config.cpp:837
virtual const char * GetName() const
Definition: C4PropList.cpp:267
C4SoundEffect * GetFirstSound() const
Definition: C4SoundSystem.h:52
C4Console Console
Definition: C4Globals.cpp:45
C4PropListStatic * GetPropList()
Definition: C4Aul.h:153
C4String * getStr() const
Definition: C4Value.h:117
bool GetProperty(C4PropertyName k, C4Value *pResult) const
Definition: C4PropList.h:103
C4AulScriptEngine ScriptEngine
Definition: C4Globals.cpp:43
#define b
const char * SSearch(const char *szString, const char *szIndex)
Definition: Standard.cpp:340
bool IsDead()
Definition: C4Effect.h:110
const char * GetCStr() const
Definition: C4StringTable.h:49
C4String * RegString(StdStrBuf String)
C4Value C4VInt(int32_t i)
Definition: C4Value.h:242
void MouseMove(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam, class C4Viewport *pVP)
Definition: C4Gui.h:2829
void SetProperty(C4PropertyName k, const C4Value &to)
Definition: C4PropList.h:122
int32_t iInterval
Definition: C4Effect.h:73
C4V_Type GetTypeEx() const
Definition: C4Value.cpp:625
void Set0()
Definition: C4Value.h:336
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:181
virtual class C4PropListStatic * IsStatic()
Definition: C4PropList.h:87
C4AulExec AulExec
Definition: C4AulExec.cpp:33
StdStrBuf GetDataString(int depth=10, const class C4PropListStatic *ignore_reference_parent=nullptr) const
Definition: C4Value.cpp:133
#define a
C4Value C4VObj(C4Object *pObj)
Definition: C4Value.cpp:90
uint32_t GetTextColorForBackground(uint32_t background_color)
virtual C4Object * GetObject()
Definition: C4PropList.cpp:644
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool IsActive()
Definition: C4Effect.h:112
const C4Value & GetItem(int32_t iElem) const
Definition: C4ValueArray.h:38
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:197
virtual C4Def const * GetDef() const
Definition: C4PropList.cpp:662
void AppendChar(char cChar)
Definition: StdBuf.h:596
C4SoundInstance * StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
C4ValueArray * _getArray() const
Definition: C4Value.h:127
int32_t GetPropertyBool(C4PropertyName n, bool default_val=false) const
Definition: C4PropList.cpp:849
void EMControl(enum C4PacketType eCtrlType, class C4ControlPacket *pCtrl)
std::vector< C4String * > GetSortedLocalProperties(bool add_prototype=true) const
Definition: C4PropList.cpp:557
C4V_Type GetType() const
Definition: C4Value.h:161
C4StringTable Strings
Definition: C4Globals.cpp:42
T * Get() const
virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const
Definition: C4PropList.cpp:734
C4Value C4VPropList(C4PropList *p)
Definition: C4Value.h:245
C4GameControl Control
virtual const char * GetName() const
Definition: C4PropList.cpp:626
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:527
int32_t _getInt() const
Definition: C4Value.h:122
int32_t iTime
Definition: C4Effect.h:73
C4ConfigDeveloper Developer
Definition: C4Config.h:253
int32_t GetX() const
Definition: C4Object.h:286
Definition: C4Def.h:100
int32_t Status
Definition: C4PropList.h:170
C4ValueArray * getArray() const
Definition: C4Value.h:118
bool IsFrozen() const
Definition: C4PropList.h:133
C4PropertyName
int32_t GetSize() const
Definition: C4ValueArray.h:36
void RemoveExtension(char *szFilename)
Definition: StdFile.cpp:316
C4ValueArray * GetPropertyArray(C4PropertyName n) const
Definition: C4PropList.cpp:766
virtual C4Effect * GetEffect()
Definition: C4PropList.cpp:686
int32_t getInt() const
Definition: C4Value.h:112
C4Value C4VBool(bool b)
Definition: C4Value.h:243
void Right(T &keys)
const C4Value C4VNull
Definition: C4Value.cpp:32
C4Def * C4Id2Def(C4ID id)
Definition: C4DefList.h:80
bool GetCurrentSelectionPosition(int32_t *x, int32_t *y)
void SetHighlightedObject(C4Object *new_highlight)
C4Object * SafeObjectPointer(int32_t iNumber)
C4Def * getDef() const
Definition: C4Value.cpp:80
C4Value C4VString(C4String *pStr)
Definition: C4Value.h:246
int32_t GetPropertyInt(C4PropertyName k, int32_t default_val=0) const
Definition: C4PropList.cpp:863
size_t getLength() const
Definition: StdBuf.h:453
C4DefList Definitions
Definition: C4Globals.cpp:49
C4Effect * pEffects
Definition: C4Object.h:156
C4ObjectPtr Contained
Definition: C4Object.h:144
bool IsIdenticalTo(const C4Value &cmp) const
Definition: C4Value.h:149
std::vector< C4String * > GetUnsortedProperties(const char *prefix, C4PropList *ignore_parent=nullptr) const
Definition: C4PropList.cpp:595
static const C4ID EditorBase
Definition: C4Id.h:48
C4PropList * _getPropList() const
Definition: C4Value.h:129
void InvalidateSelection()
Definition: C4EditCursor.h:153
C4Value Call(C4PropertyName k, C4AulParSet *pPars=0, bool fPassErrors=false)
Definition: C4PropList.h:112
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:253
void Copy()
Definition: StdBuf.h:475
C4String * GetPropertyStr(C4PropertyName k) const
Definition: C4PropList.cpp:752
#define s
C4PropList * GetPropertyPropList(C4PropertyName k) const
Definition: C4PropList.cpp:877
std::vector< C4Def * > GetAllDefs(C4String *filter_property=nullptr) const
Definition: C4DefList.cpp:290
virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const
Definition: C4Object.cpp:4880
C4Object * getObj() const
Definition: C4Value.cpp:70
C4Application Application
Definition: C4Globals.cpp:44
C4Value DirectExec(C4PropList *p, const char *szScript, const char *szContext, bool fPassErrors=false, C4AulScriptContext *context=nullptr, bool parse_function=false)
Definition: C4AulExec.cpp:1018
const C4PropListStatic * GetParent() const
Definition: C4PropList.h:272
C4Effect * pGlobalEffects
Definition: C4Aul.h:146
C4GameObjects Objects
Definition: C4Globals.cpp:48
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:277
StdStrBuf GetDataString() const
Definition: C4PropList.cpp:253
C4PropList * getPropList() const
Definition: C4Value.h:116