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