OpenClonk
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  use_path.SetProperty(option_value, ignore_base_props_static);
1498  }
1499  factory->GetPropertyModel()->DoOnUpdateCall(use_path, this);
1500 }
1501 
1502 QWidget *C4PropertyDelegateEnum::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
1503 {
1504  Editor *editor = new Editor(parent);
1505  editor->layout = new QHBoxLayout(editor);
1506  editor->layout->setContentsMargins(0, 0, 0, 0);
1507  editor->layout->setMargin(0);
1508  editor->layout->setSpacing(0);
1509  editor->updating = true;
1510  editor->option_box = new C4DeepQComboBox(editor, GetOptionComboBoxButtonType(), allow_editing);
1511  editor->layout->addWidget(editor->option_box);
1512  for (auto &option : options) editor->option_box->addItem(option.name->GetCStr());
1513  editor->option_box->setModel(CreateOptionModel());
1514  editor->option_box->model()->setParent(editor->option_box);
1515  // Signal for selecting a new entry from the dropdown menu
1516  connect(editor->option_box, &C4DeepQComboBox::NewItemSelected, editor, [editor, this](int32_t newval) {
1517  if (!editor->updating) this->UpdateOptionIndex(editor, newval, nullptr); });
1518  // Signal for write-in on enum delegates that allow editing
1519  if (allow_editing)
1520  {
1521  connect(editor->option_box, &C4DeepQComboBox::TextChanged, editor, [editor, this](const QString &new_text) {
1522  if (!editor->updating)
1523  {
1524  this->UpdateOptionIndex(editor, GetOptionByValue(C4VString(new_text.toUtf8())), &new_text);
1525  }
1526  });
1527  }
1528 
1529  editor->updating = false;
1530  // If created by a selection from a parent enum, show drop down immediately after value has been set
1531  editor->dropdown_pending = by_selection && is_child;
1532  return editor;
1533 }
1534 
1535 void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *editor, int newval, const QString *custom_text) const
1536 {
1537  bool has_changed = false;
1538  // Change by text entry?
1539  if (custom_text)
1540  {
1541  C4String *last_value_string = editor->last_val.getStr();
1542  if (!last_value_string || last_value_string->GetData() != custom_text->toUtf8())
1543  {
1544  editor->last_val = C4VString(custom_text->toUtf8());
1545  has_changed = true;
1546  }
1547  }
1548  // Update value and parameter delegate if selection changed
1549  if (newval != editor->last_selection_index)
1550  {
1551  editor->last_selection_index = newval;
1552  UpdateEditorParameter(editor, !custom_text);
1553  has_changed = true;
1554  }
1555  // Change either by text entry or by dropdown selection: Emit signal to parent
1556  if (has_changed)
1557  {
1558  editor->option_changed = true;
1559  emit EditorValueChangedSignal(editor);
1560  }
1561 }
1562 
1563 void C4PropertyDelegateEnum::EnsureOptionDelegateResolved(const Option &option) const
1564 {
1565  // Lazy-resolve parameter delegate
1566  if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil)
1567  option.adelegate = factory->GetDelegateByValue(option.adelegate_val);
1568 }
1569 
1570 QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1571 {
1572  // Display string from value
1573  int32_t idx = GetOptionByValue(v);
1574  if (idx == Editor::INDEX_Custom_Value)
1575  {
1576  // Value not found: Default display of strings; full display of nonsense values for debugging purposes.
1577  C4String *custom_string = v.getStr();
1578  if (custom_string)
1579  {
1580  return QString(custom_string->GetCStr());
1581  }
1582  else
1583  {
1584  return C4PropertyDelegate::GetDisplayString(v, obj, short_names);
1585  }
1586  }
1587  else
1588  {
1589  // Value found: Display option string plus parameter
1590  const Option &option = options[idx];
1591  QString result = (short_names ? option.short_name : option.name)->GetCStr();
1592  // Lazy-resolve parameter delegate
1593  EnsureOptionDelegateResolved(option);
1594  if (option.adelegate)
1595  {
1596  C4Value param_val = v;
1597  if (option.value_key.Get())
1598  {
1599  param_val.Set0();
1600  C4PropList *vp = v.getPropList();
1601  if (vp) vp->GetPropertyByS(option.value_key, &param_val);
1602  }
1603  if (!result.isEmpty()) result += " ";
1604  result += option.adelegate->GetDisplayString(param_val, obj, short_names);
1605  }
1606  return result;
1607  }
1608 }
1609 
1610 const C4PropertyDelegateShape *C4PropertyDelegateEnum::GetShapeDelegate(C4Value &val, C4PropertyPath *shape_path) const
1611 {
1612  // Does this delegate own a shape? Forward decision into selected option.
1613  int32_t option_idx = GetOptionByValue(val);
1614  if (option_idx == Editor::INDEX_Custom_Value) return nullptr;
1615  const Option &option = options[option_idx];
1616  EnsureOptionDelegateResolved(option);
1617  if (!option.adelegate) return nullptr;
1618  if (option.value_key.Get())
1619  {
1620  *shape_path = option.adelegate->GetPathForProperty(*shape_path, option.value_key->GetCStr());
1621  C4PropList *vp = val.getPropList();
1622  val.Set0();
1623  if (vp) vp->GetPropertyByS(option.value_key, &val);
1624  }
1625  return option.adelegate->GetShapeDelegate(val, shape_path);
1626 }
1627 
1628 bool C4PropertyDelegateEnum::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const
1629 {
1630  // Custom painting: Forward to selected child delegate
1631  int32_t option_idx = GetOptionByValue(val);
1632  if (option_idx == Editor::INDEX_Custom_Value) return false;
1633  const Option &selected_option = options[option_idx];
1634  EnsureOptionDelegateResolved(selected_option);
1635  if (!selected_option.adelegate) return false;
1636  if (selected_option.adelegate->HasCustomPaint())
1637  {
1638  QStyleOptionViewItem parameter_option = QStyleOptionViewItem(option);
1639  parameter_option.rect.adjust(parameter_option.rect.width()/2, 0, 0, 0);
1640  C4Value parameter_val = val;
1641  if (selected_option.value_key.Get())
1642  {
1643  parameter_val.Set0();
1644  C4PropList *vp = val.getPropList();
1645  if (vp) vp->GetPropertyByS(selected_option.value_key, &parameter_val);
1646  }
1647  selected_option.adelegate->Paint(painter, parameter_option, parameter_val);
1648  }
1649  // Always return false to draw self using the standard method
1650  return false;
1651 }
1652 
1653 bool C4PropertyDelegateEnum::IsPasteValid(const C4Value &val) const
1654 {
1655  // Strings always OK in editable enums
1656  if (val.GetType() == C4V_String && allow_editing) return true;
1657  // Must be a valid selection
1658  int32_t option_idx = GetOptionByValue(val);
1659  if (option_idx == Editor::INDEX_Custom_Value) return false;
1660  const Option &option = options[option_idx];
1661  // Check validity for parameter
1662  EnsureOptionDelegateResolved(option);
1663  if (!option.adelegate) return true; // No delegate? Then any value is OK.
1664  C4Value parameter_val;
1665  if (option.value_key.Get())
1666  {
1667  C4PropList *vp = val.getPropList();
1668  if (!vp) return false;
1669  vp->GetPropertyByS(option.value_key, &parameter_val); // if this fails, check parameter against nil
1670  }
1671  else
1672  {
1673  parameter_val = val;
1674  }
1675  return option.adelegate->IsPasteValid(parameter_val);
1676 }
1677 
1678 
1679 /* Definition delegate */
1680 
1681 C4PropertyDelegateDef::C4PropertyDelegateDef(const C4PropertyDelegateFactory *factory, C4PropList *props)
1682  : C4PropertyDelegateEnum(factory, props)
1683 {
1684  // nil is always an option
1685  AddConstOption(empty_name ? empty_name.Get() : ::Strings.RegString("nil"), C4VNull);
1686  // Collect sorted definitions
1687  filter_property = props ? props->GetPropertyStr(P_Filter) : nullptr;
1688  if (filter_property)
1689  {
1690  // With filter just create a flat list
1691  std::vector<C4Def *> defs = ::Definitions.GetAllDefs(filter_property);
1692  std::sort(defs.begin(), defs.end(), [](C4Def *a, C4Def *b) -> bool {
1693  return strcmp(a->GetName(), b->GetName()) < 0;
1694  });
1695  // Add them
1696  for (C4Def *def : defs)
1697  {
1698  C4RefCntPointer<C4String> option_name = ::Strings.RegString(FormatString("%s (%s)", def->id.ToString(), def->GetName()));
1699  AddConstOption(option_name, C4Value(def), nullptr);
1700  }
1701  }
1702  else
1703  {
1704  // Without filter copy tree from definition list model
1705  C4ConsoleQtDefinitionListModel *def_list_model = factory->GetDefinitionListModel();
1706  // Recursively add all defs from model
1707  AddDefinitions(def_list_model, QModelIndex(), nullptr);
1708  }
1709 }
1710 
1711 void C4PropertyDelegateDef::AddDefinitions(C4ConsoleQtDefinitionListModel *def_list_model, QModelIndex parent, C4String *group)
1712 {
1713  int32_t count = def_list_model->rowCount(parent);
1714  for (int32_t i = 0; i < count; ++i)
1715  {
1716  QModelIndex index = def_list_model->index(i, 0, parent);
1717  C4Def *def = def_list_model->GetDefByModelIndex(index);
1718  C4RefCntPointer<C4String> name = ::Strings.RegString(def_list_model->GetNameByModelIndex(index));
1719  if (def) AddConstOption(name.Get(), C4Value(def), group);
1720  if (def_list_model->rowCount(index))
1721  {
1722  AddDefinitions(def_list_model, index, group ? ::Strings.RegString(FormatString("%s/%s", group->GetCStr(), name->GetCStr()).getData()) : name.Get());
1723  }
1724  }
1725 }
1726 
1727 bool C4PropertyDelegateDef::IsPasteValid(const C4Value &val) const
1728 {
1729  // Must be a definition or nil
1730  if (val.GetType() == C4V_Nil) return true;
1731  C4Def *def = val.getDef();
1732  if (!def) return false;
1733  // Check filter
1734  if (filter_property)
1735  {
1736  C4Value prop_val;
1737  if (!def->GetPropertyByS(filter_property, &prop_val)) return false;
1738  if (!prop_val) return false;
1739  }
1740  return true;
1741 }
1742 
1743 
1744 /* Object delegate */
1745 
1746 C4PropertyDelegateObject::C4PropertyDelegateObject(const C4PropertyDelegateFactory *factory, C4PropList *props)
1747  : C4PropertyDelegateEnum(factory, props), max_nearby_objects(20)
1748 {
1749  // Settings
1750  if (props)
1751  {
1752  filter = props->GetPropertyStr(P_Filter);
1753  }
1754  // Actual object list is created/updated when the editor is created
1755 }
1756 
1757 C4RefCntPointer<C4String> C4PropertyDelegateObject::GetObjectEntryString(C4Object *obj) const
1758 {
1759  // Compose object display string from containment(*), name, position (@x,y) and object number (#n)
1760  return ::Strings.RegString(FormatString("%s%s @%d,%d (#%d)", obj->Contained ? "*" : "", obj->GetName(), (int)obj->GetX(), (int)obj->GetY(), (int)obj->Number));
1761 }
1762 
1763 void C4PropertyDelegateObject::UpdateObjectList()
1764 {
1765  // Re-create object list from current position
1766  ClearOptions();
1767  // Get matching objects first
1768  std::vector<C4Object *> objects;
1769  for (C4Object *obj : ::Objects) if (obj->Status)
1770  {
1771  C4Value filter_val;
1772  if (filter)
1773  {
1774  if (!obj->GetPropertyByS(filter, &filter_val)) continue;
1775  if (!filter_val) continue;
1776  }
1777  objects.push_back(obj);
1778  }
1779  // Get list sorted by distance from selected object
1780  std::vector<C4Object *> objects_by_distance;
1781  int32_t cx=0, cy=0;
1783  {
1784  objects_by_distance = objects;
1785  auto ObjDist = [cx, cy](C4Object *o) { return (o->GetX() - cx)*(o->GetX() - cx) + (o->GetY() - cy)*(o->GetY() - cy); };
1786  std::stable_sort(objects_by_distance.begin(), objects_by_distance.end(), [&ObjDist](C4Object *a, C4Object *b) { return ObjDist(a) < ObjDist(b); });
1787  }
1788  size_t num_nearby = objects_by_distance.size();
1789  bool has_all_objects_list = (num_nearby > max_nearby_objects);
1790  if (has_all_objects_list) num_nearby = max_nearby_objects;
1791  // Add actual objects
1792  ReserveOptions(1 + num_nearby + !!num_nearby + (has_all_objects_list ? objects.size() : 0));
1793  AddConstOption(::Strings.RegString("nil"), C4VNull); // nil is always an option
1794  if (num_nearby)
1795  {
1796  // TODO: "Select object" entry
1797  //AddCallbackOption(LoadResStr("IDS_CNS_SELECTOBJECT"));
1798  // Nearby list
1799  C4RefCntPointer<C4String> nearby_group;
1800  // If there are main objects, Create a subgroup. Otherwise, just put all elements into the main group.
1801  if (has_all_objects_list) nearby_group = ::Strings.RegString(LoadResStr("IDS_CNS_NEARBYOBJECTS"));
1802  for (int32_t i = 0; i < num_nearby; ++i)
1803  {
1804  C4Object *obj = objects_by_distance[i];
1805  AddConstOption(GetObjectEntryString(obj).Get(), C4VObj(obj), nearby_group.Get());
1806  }
1807  // All objects
1808  if (has_all_objects_list)
1809  {
1810  C4RefCntPointer<C4String> all_group = ::Strings.RegString(LoadResStr("IDS_CNS_ALLOBJECTS"));
1811  for (C4Object *obj : objects) AddConstOption(GetObjectEntryString(obj).Get(), C4VObj(obj), all_group.Get());
1812  }
1813  }
1814 }
1815 
1816 QWidget *C4PropertyDelegateObject::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
1817 {
1818  // Update object list for created editor
1819  // (This should be safe since the object delegate cannot contain nested delegates)
1820  const_cast<C4PropertyDelegateObject *>(this)->UpdateObjectList();
1821  return C4PropertyDelegateEnum::CreateEditor(parent_delegate, parent, option, by_selection, is_child);
1822 }
1823 
1824 QString C4PropertyDelegateObject::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1825 {
1826  C4Object *vobj = v.getObj();
1827  if (vobj)
1828  {
1829  C4RefCntPointer<C4String> s = GetObjectEntryString(vobj);
1830  return QString(s->GetCStr());
1831  }
1832  else
1833  {
1834  return QString(v.GetDataString().getData());
1835  }
1836 }
1837 
1838 bool C4PropertyDelegateObject::IsPasteValid(const C4Value &val) const
1839 {
1840  // Must be an object or nil
1841  if (val.GetType() == C4V_Nil) return true;
1842  C4Object *obj = val.getObj();
1843  if (!obj) return false;
1844  // Check filter
1845  if (filter)
1846  {
1847  C4Value prop_val;
1848  if (!obj->GetPropertyByS(filter, &prop_val)) return false;
1849  if (!prop_val) return false;
1850  }
1851  return true;
1852 }
1853 
1854 
1855 /* Sound delegate */
1856 
1857 C4PropertyDelegateSound::C4PropertyDelegateSound(const C4PropertyDelegateFactory *factory, C4PropList *props)
1858  : C4PropertyDelegateEnum(factory, props)
1859 {
1860  // Add none-option
1861  AddConstOption(::Strings.RegString("nil"), C4VNull);
1862  // Add all sounds as options
1863  for (C4SoundEffect *fx = ::Application.SoundSystem.GetFirstSound(); fx; fx = fx->Next)
1864  {
1865  // Extract group name as path to sound, replacing "::" by "/" for enum groups
1866  StdStrBuf full_name_s(fx->GetFullName(), true);
1867  RemoveExtension(&full_name_s);
1868  const char *full_name = full_name_s.getData();
1869  const char *base_name = full_name, *pos;
1870  StdStrBuf group_string;
1871  while ((pos = SSearch(base_name, "::")))
1872  {
1873  if (group_string.getLength()) group_string.AppendChar('/');
1874  group_string.Append(base_name, pos - base_name - 2);
1875  base_name = pos;
1876  }
1878  if (group_string.getLength()) group = ::Strings.RegString(group_string);
1879  // Script name: Full name (without extension)
1880  C4RefCntPointer<C4String> sound_string = ::Strings.RegString(full_name_s);
1881  // Add the option
1882  AddConstOption(::Strings.RegString(base_name), C4VString(sound_string.Get()), group.Get(), sound_string.Get());
1883  }
1884 }
1885 
1886 QString C4PropertyDelegateSound::GetDisplayString(const C4Value &v, class C4Object *obj, bool short_names) const
1887 {
1888  // Always show full sound name
1889  C4String *val_string = v.getStr();
1890  return val_string ? QString(val_string->GetCStr()) : QString(v.GetDataString().getData());
1891 }
1892 
1893 bool C4PropertyDelegateSound::IsPasteValid(const C4Value &val) const
1894 {
1895  // Must be nil or a string
1896  if (val.GetType() == C4V_Nil) return true;
1897  if (val.GetType() != C4V_String) return false;
1898  return true;
1899 }
1900 
1901 
1902 /* Boolean delegate */
1903 
1904 C4PropertyDelegateBool::C4PropertyDelegateBool(const C4PropertyDelegateFactory *factory, C4PropList *props)
1905  : C4PropertyDelegateEnum(factory, props)
1906 {
1907  // Add boolean options
1908  ReserveOptions(2);
1909  AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_FALSE")), C4VBool(false));
1910  AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_TRUE")), C4VBool(true));
1911 }
1912 
1913 bool C4PropertyDelegateBool::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
1914 {
1915  // Force value to bool
1916  bool success = C4PropertyDelegateEnum::GetPropertyValue(container, key, index, out_val);
1917  if (out_val->GetType() != C4V_Bool) *out_val = C4VBool(!!*out_val);
1918  return success;
1919 }
1920 
1921 bool C4PropertyDelegateBool::IsPasteValid(const C4Value &val) const
1922 {
1923  // Must be a boolean
1924  if (val.GetType() != C4V_Bool) return false;
1925  return true;
1926 }
1927 
1928 
1929 /* Has-effect delegate */
1930 
1931 C4PropertyDelegateHasEffect::C4PropertyDelegateHasEffect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
1932  : C4PropertyDelegateBool(factory, props)
1933 {
1934  if (props) effect = props->GetPropertyStr(P_Effect);
1935 }
1936 
1937 bool C4PropertyDelegateHasEffect::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
1938 {
1939  const C4Object *obj = container.getObj();
1940  if (obj && effect)
1941  {
1942  bool has_effect = false;
1943  for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
1944  if (!fx->IsDead())
1945  if (!strcmp(fx->GetName(), effect->GetCStr()))
1946  {
1947  has_effect = true;
1948  break;
1949  }
1950  *out_val = C4VBool(has_effect);
1951  return true;
1952  }
1953  return false;
1954 }
1955 
1956 
1957 /* C4Value via an enumeration delegate */
1958 
1959 C4PropertyDelegateC4ValueEnum::C4PropertyDelegateC4ValueEnum(const C4PropertyDelegateFactory *factory, C4PropList *props)
1960  : C4PropertyDelegateEnum(factory, props)
1961 {
1962  // Add default C4Value selections
1963  ReserveOptions(10);
1964  AddTypeOption(::Strings.RegString("nil"), C4V_Nil, C4VNull);
1965  AddTypeOption(::Strings.RegString("bool"), C4V_Bool, C4VNull, factory->GetDelegateByValue(C4VString("bool")));
1966  AddTypeOption(::Strings.RegString("int"), C4V_Int, C4VNull, factory->GetDelegateByValue(C4VString("int")));
1967  AddTypeOption(::Strings.RegString("string"), C4V_String, C4VNull, factory->GetDelegateByValue(C4VString("string")));
1968  AddTypeOption(::Strings.RegString("array"), C4V_Array, C4VNull, factory->GetDelegateByValue(C4VString("array")));
1969  AddTypeOption(::Strings.RegString("function"), C4V_Function, C4VNull, factory->GetDelegateByValue(C4VString("function")));
1970  AddTypeOption(::Strings.RegString("object"), C4V_Object, C4VNull, factory->GetDelegateByValue(C4VString("object")));
1971  AddTypeOption(::Strings.RegString("def"), C4V_Def, C4VNull, factory->GetDelegateByValue(C4VString("def")));
1972  AddTypeOption(::Strings.RegString("effect"), C4V_Effect, C4VNull, factory->GetDelegateByValue(C4VString("effect")));
1973  AddTypeOption(::Strings.RegString("proplist"), C4V_PropList, C4VNull, factory->GetDelegateByValue(C4VString("proplist")));
1974 }
1975 
1976 
1977 /* C4Value via an edit field delegate */
1978 
1979 C4PropertyDelegateC4ValueInputEditor::C4PropertyDelegateC4ValueInputEditor(QWidget *parent)
1980  : QWidget(parent), layout(nullptr), edit(nullptr), extended_button(nullptr), commit_pending(false)
1981 {
1982  layout = new QHBoxLayout(this);
1983  layout->setContentsMargins(0, 0, 0, 0);
1984  layout->setMargin(0);
1985  layout->setSpacing(0);
1986  edit = new QLineEdit(this);
1987  layout->addWidget(edit);
1988  extended_button = new QPushButton("...", this);
1989  extended_button->setMaximumWidth(extended_button->fontMetrics().boundingRect("...").width() + 6);
1990  layout->addWidget(extended_button);
1991  extended_button->hide();
1992  edit->setFocus();
1993  setLayout(layout);
1994 }
1995 
1996 void C4PropertyDelegateC4ValueInput::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
1997 {
1998  Editor *editor = static_cast<Editor *>(aeditor);
1999  editor->edit->setText(val.GetDataString().getData());
2000  if (val.GetType() == C4V_PropList || val.GetType() == C4V_Array)
2001  {
2002  editor->extended_button->show();
2003  editor->property_path = property_path;
2004  }
2005  else
2006  {
2007  editor->extended_button->hide();
2008  }
2009 }
2010 
2011 void C4PropertyDelegateC4ValueInput::SetModelData(QObject *aeditor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
2012 {
2013  // Only set model data when pressing Enter explicitely; not just when leaving
2014  Editor *editor = static_cast<Editor *>(aeditor);
2015  if (editor->commit_pending)
2016  {
2017  property_path.SetProperty(editor->edit->text().toUtf8());
2018  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
2019  editor->commit_pending = false;
2020  }
2021 }
2022 
2023 QWidget *C4PropertyDelegateC4ValueInput::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection, bool is_child) const
2024 {
2025  // Editor is just an edit box plus a "..." button for array/proplist types
2026  Editor *editor = new Editor(parent);
2027  // EditingDone only on Return; not just when leaving edit field
2028  connect(editor->edit, &QLineEdit::returnPressed, editor, [this, editor]() {
2029  editor->commit_pending = true;
2030  emit EditingDoneSignal(editor);
2031  });
2032  connect(editor->extended_button, &QPushButton::pressed, editor, [this, editor]() {
2033  C4Value val = editor->property_path.ResolveValue();
2034  if (val.getPropList() || val.getArray())
2035  {
2036  this->factory->GetPropertyModel()->DescendPath(val, val.getPropList(), editor->property_path);
2037  ::Console.EditCursor.InvalidateSelection();
2038  }
2039  });
2040  // Selection in child enum: Direct focus
2041  if (by_selection && is_child) editor->edit->setFocus();
2042  return editor;
2043 }
2044 
2045 
2046 /* Areas shown in viewport */
2047 
2048 C4PropertyDelegateShape::C4PropertyDelegateShape(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2049  : C4PropertyDelegate(factory, props), clr(0xffff0000)
2050 {
2051  if (props)
2052  {
2053  clr = props->GetPropertyInt(P_Color) | 0xff000000;
2054  }
2055 }
2056 
2057 void C4PropertyDelegateShape::SetModelData(QObject *editor, const C4PropertyPath &property_path, C4ConsoleQtShape *prop_shape) const
2058 {
2059  // Only set shape data if triggered through shape movement signal; ignore update calls from e.g. parent enum editor
2060  if (!editor)
2061  {
2062  if (prop_shape && prop_shape->GetParentDelegate() == this)
2063  {
2064  property_path.SetProperty(prop_shape->GetValue());
2065  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
2066  }
2067  }
2068 }
2069 
2070 bool C4PropertyDelegateShape::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const
2071 {
2072  // Background color
2073  if (option.state & QStyle::State_Selected)
2074  painter->fillRect(option.rect, option.palette.highlight());
2075  else
2076  painter->fillRect(option.rect, option.palette.base());
2077  // Draw a frame in shape color
2078  painter->save();
2079  QColor frame_color = QColor(QRgb(clr & 0xffffff));
2080  int32_t width = Clamp<int32_t>(option.rect.height() / 8, 2, 6) &~1;
2081  QPen rect_pen(QBrush(frame_color), width, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
2082  painter->setPen(rect_pen);
2083  QRect inner_rect = option.rect.adjusted(width / 2, width / 2, -width / 2, -width / 2);
2084  if (inner_rect.width() > inner_rect.height())
2085  {
2086  // Draw shape in right corner
2087  inner_rect.adjust(inner_rect.width() - inner_rect.height(), 0, 0, 0);
2088  }
2089  // Paint by shape type
2090  DoPaint(painter, inner_rect);
2091  // Done painting
2092  painter->restore();
2093  return true;
2094 }
2095 
2096 void C4PropertyDelegateShape::ConnectSignals(C4ConsoleQtShape *shape, const C4PropertyPath &property_path) const
2097 {
2098  connect(shape, &C4ConsoleQtShape::ShapeDragged, this, [this, shape, property_path]() {
2099  this->SetModelData(nullptr, property_path, shape);
2100  });
2101 }
2102 
2103 /* Areas shown in viewport: Rectangle */
2104 
2105 C4PropertyDelegateRect::C4PropertyDelegateRect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2106  : C4PropertyDelegateShape(factory, props)
2107 {
2108  if (props)
2109  {
2110  storage = props->GetPropertyStr(P_Storage);
2111  }
2112 }
2113 
2114 void C4PropertyDelegateRect::DoPaint(QPainter *painter, const QRect &inner_rect) const
2115 {
2116  painter->drawRect(inner_rect);
2117 }
2118 
2119 bool C4PropertyDelegateRect::IsPasteValid(const C4Value &val) const
2120 {
2121  // Check storage as prop list
2122  if (storage)
2123  {
2124  // Proplist-stored rect must have defined properties
2125  C4PropertyName def_property_names[2][4] = { { P_x, P_y, P_wdt, P_hgt },{ P_X, P_Y, P_Wdt, P_Hgt } };
2126  C4PropertyName *property_names = nullptr;
2127  if (storage == &::Strings.P[P_proplist])
2128  {
2129  property_names = def_property_names[0];
2130  }
2131  else if (storage == &::Strings.P[P_Proplist])
2132  {
2133  property_names = def_property_names[1];
2134  }
2135  if (property_names)
2136  {
2137  C4PropList *val_proplist = val.getPropList();
2138  if (!val_proplist) return false;
2139  for (int32_t i = 0; i < 4; ++i)
2140  {
2141  C4Value propval;
2142  if (!val_proplist->GetProperty(property_names[i], &propval)) return false;
2143  if (propval.GetType() != C4V_Int) return false;
2144  }
2145  // extra properties are OK
2146  }
2147  return true;
2148  }
2149  // Check storage as array: Expect array with four elements. Width and height non-negative.
2150  C4ValueArray *val_arr = val.getArray();
2151  if (!val_arr || val_arr->GetSize() != 4) return false;
2152  for (int32_t i = 0; i < 4; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2153  if (val_arr->GetItem(2)._getInt() < 0) return false;
2154  if (val_arr->GetItem(3)._getInt() < 0) return false;
2155  return true;
2156 }
2157 
2158 
2159 /* Areas shown in viewport: Circle */
2160 
2161 C4PropertyDelegateCircle::C4PropertyDelegateCircle(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2162  : C4PropertyDelegateShape(factory, props)
2163 {
2164  if (props)
2165  {
2166  can_move_center = props->GetPropertyBool(P_CanMoveCenter);
2167  }
2168 }
2169 
2170 void C4PropertyDelegateCircle::DoPaint(QPainter *painter, const QRect &inner_rect) const
2171 {
2172  painter->drawEllipse(inner_rect);
2173  if (can_move_center) painter->drawPoint(inner_rect.center());
2174 }
2175 
2176 bool C4PropertyDelegateCircle::IsPasteValid(const C4Value &val) const
2177 {
2178  // Circle radius stored as single non-negative int
2179  if (!can_move_center) return (val.GetType() == C4V_Int) && (val.getInt() >= 0);
2180  // Circle+Center stored as array with three elements (radius, x, y)
2181  C4ValueArray *val_arr = val.getArray();
2182  if (!val_arr || val_arr->GetSize() != 3) return false;
2183  for (int32_t i = 0; i < 3; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2184  if (val_arr->GetItem(0)._getInt() < 0) return false;
2185  return true;
2186 }
2187 
2188 
2189 /* Areas shown in viewport: Point */
2190 
2191 C4PropertyDelegatePoint::C4PropertyDelegatePoint(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2192  : C4PropertyDelegateShape(factory, props)
2193 {
2194  if (props)
2195  {
2196  horizontal_fix = props->GetPropertyBool(P_HorizontalFix);
2197  vertical_fix = props->GetPropertyBool(P_VerticalFix);
2198  }
2199 }
2200 
2201 void C4PropertyDelegatePoint::DoPaint(QPainter *painter, const QRect &inner_rect) const
2202 {
2203  QPoint ctr = inner_rect.center();
2204  int r = inner_rect.height() * 7 / 20;
2205  if (horizontal_fix && !vertical_fix)
2206  {
2207  painter->drawLine(ctr + QPoint(0, -r), ctr + QPoint(0, +r));
2208  painter->drawLine(ctr + QPoint(-r / 2, -r), ctr + QPoint(+r / 2, -r));
2209  painter->drawLine(ctr + QPoint(-r / 2, +r), ctr + QPoint(+r / 2, +r));
2210  }
2211  else if (vertical_fix && !horizontal_fix)
2212  {
2213  painter->drawLine(ctr + QPoint(-r, 0), ctr + QPoint(+r, 0));
2214  painter->drawLine(ctr + QPoint(-r, -r / 2), ctr + QPoint(-r, +r / 2));
2215  painter->drawLine(ctr + QPoint(+r, -r / 2), ctr + QPoint(+r, +r / 2));
2216  }
2217  else
2218  {
2219  if (!horizontal_fix)
2220  {
2221  painter->drawLine(ctr + QPoint(-r, -r), ctr + QPoint(+r, +r));
2222  }
2223  painter->drawLine(ctr + QPoint(+r, -r), ctr + QPoint(-r, +r));
2224  painter->drawEllipse(inner_rect);
2225  }
2226 }
2227 
2228 bool C4PropertyDelegatePoint::IsPasteValid(const C4Value &val) const
2229 {
2230  // Point stored as array with two elements
2231  C4ValueArray *val_arr = val.getArray();
2232  if (!val_arr || val_arr->GetSize() != 2) return false;
2233  for (int32_t i = 0; i < 2; ++i) if (val_arr->GetItem(i).GetType() != C4V_Int) return false;
2234  return true;
2235 }
2236 
2237 
2238 /* Areas shown in viewport: Graph */
2239 
2240 C4PropertyDelegateGraph::C4PropertyDelegateGraph(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2241  : C4PropertyDelegateShape(factory, props)
2242 {
2243  if (props)
2244  {
2245  horizontal_fix = props->GetPropertyBool(P_HorizontalFix);
2246  vertical_fix = props->GetPropertyBool(P_VerticalFix);
2247  structure_fix = props->GetPropertyBool(P_StructureFix);
2248  }
2249 }
2250 
2251 void C4PropertyDelegateGraph::DoPaint(QPainter *painter, const QRect &inner_rect) const
2252 {
2253  // Draw symbol as a bunch of connected lines
2254  QPoint ctr = inner_rect.center();
2255  int r = inner_rect.height() * 7 / 20;
2256  painter->drawLine(ctr, ctr + QPoint(-r / 2, -r));
2257  painter->drawLine(ctr, ctr + QPoint(+r / 2, -r));
2258  painter->drawLine(ctr, ctr + QPoint(0, +r));
2259 }
2260 
2261 bool C4PropertyDelegateGraph::IsVertexPasteValid(const C4Value &val) const
2262 {
2263  // Check that it's an array of at least one point
2264  const C4ValueArray *arr = val.getArray();
2265  if (!arr || !arr->GetSize()) return false;
2266  // Check validity of each point
2267  const int32_t n_props = 2;
2268  C4PropertyName property_names[n_props] = { P_X, P_Y };
2269  for (int32_t i_pt = 0; i_pt < arr->GetSize(); ++i_pt)
2270  {
2271  const C4Value &pt = arr->GetItem(i_pt);
2272  const C4PropList *ptp = pt.getPropList();
2273  if (!ptp) return false;
2274  for (auto & property_name : property_names)
2275  {
2276  C4Value ptprop;
2277  if (!ptp->GetProperty(property_name, &ptprop)) return false;
2278  if (ptprop.GetType() != C4V_Int) return false;
2279  }
2280  }
2281  return true;
2282 }
2283 
2284 bool C4PropertyDelegateGraph::IsEdgePasteValid(const C4Value &val) const
2285 {
2286  // Check that it's an array
2287  // Empty is OK; it could be a graph with one vertex and no edges
2288  const C4ValueArray *arr = val.getArray();
2289  if (!arr || !arr->GetSize()) return false;
2290  // Check validity of each edge
2291  for (int32_t i_pt = 0; i_pt < arr->GetSize(); ++i_pt)
2292  {
2293  const C4Value pt = arr->GetItem(i_pt);
2294  const C4ValueArray *pta;
2295  const C4PropList *ptp = pt.getPropList();
2296  if (!ptp) return false;
2297  pta = ptp->GetPropertyArray(P_Vertices);
2298  if (!pta) return false;
2299  // Needs two vertices (may have more values which are ignored)
2300  if (pta->GetSize() < 2) return false;
2301  }
2302  return true;
2303 }
2304 
2305 bool C4PropertyDelegateGraph::IsPasteValid(const C4Value &val) const
2306 {
2307  // Unfortunately, there is no good way to determine the correct value for fixed structure / position graph pastes
2308  // So just reject pastes for now
2309  // (TODO: Could store a default structure in a property and compare to that)
2310  if (horizontal_fix || vertical_fix || structure_fix) return false;
2311  // Check storage as prop list
2312  const int32_t n_props = 2;
2313  C4Value prop_vals[n_props]; // vertices & edges
2314  C4PropertyName property_names[n_props] = { P_Vertices, P_Edges };
2315  C4PropList *val_proplist = val.getPropList();
2316  if (!val_proplist) return false;
2317  for (int32_t i = 0; i < n_props; ++i)
2318  {
2319  val_proplist->GetProperty(property_names[i], &prop_vals[i]);
2320  }
2321  // extra properties are OK
2322  // Check validity of vertices and edges
2323  return IsVertexPasteValid(prop_vals[0]) && IsEdgePasteValid(prop_vals[1]);
2324 }
2325 
2326 void C4PropertyDelegateGraph::ConnectSignals(C4ConsoleQtShape *shape, const C4PropertyPath &property_path) const
2327 {
2328  C4ConsoleQtGraph *shape_graph = static_cast<C4ConsoleQtGraph *>(shape);
2329  connect(shape_graph, &C4ConsoleQtGraph::GraphEdit, this, [this, shape, property_path](C4ControlEditGraph::Action action, int32_t index, int32_t x, int32_t y) {
2330  // Send graph editing via queue
2331  ::Control.DoInput(CID_EditGraph, new C4ControlEditGraph(property_path.GetGetPath(), action, index, x, y), CDT_Decide);
2332  // Also send update callback to root object
2333  factory->GetPropertyModel()->DoOnUpdateCall(property_path, this);
2334  });
2335  connect(shape, &C4ConsoleQtShape::BorderSelectionChanged, this, []() {
2336  // Different part of the shape selected: Refresh info on next update
2338  });
2339 }
2340 
2341 
2342 
2343 /* Areas shown in viewport: Polyline */
2344 
2345 C4PropertyDelegatePolyline::C4PropertyDelegatePolyline(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2346  : C4PropertyDelegateGraph(factory, props)
2347 {
2348 }
2349 
2350 void C4PropertyDelegatePolyline::DoPaint(QPainter *painter, const QRect &inner_rect) const
2351 {
2352  // Draw symbol as a sequence of connected lines
2353  QPoint ctr = inner_rect.center();
2354  int r = inner_rect.height() * 7 / 20;
2355  painter->drawLine(ctr + QPoint(-r, +r), ctr + QPoint(-r/3, -r));
2356  painter->drawLine(ctr + QPoint(-r / 3, -r), ctr + QPoint(+r / 3, +r));
2357  painter->drawLine(ctr + QPoint(+r / 3, +r), ctr + QPoint(+r, -r));
2358 }
2359 
2360 bool C4PropertyDelegatePolyline::IsPasteValid(const C4Value &val) const
2361 {
2362  // Expect just a vertex array
2363  return IsVertexPasteValid(val);
2364 }
2365 
2366 
2367 /* Areas shown in viewport: Closed polyon */
2368 
2369 C4PropertyDelegatePolygon::C4PropertyDelegatePolygon(const class C4PropertyDelegateFactory *factory, C4PropList *props)
2370  : C4PropertyDelegateGraph(factory, props)
2371 {
2372 }
2373 
2374 void C4PropertyDelegatePolygon::DoPaint(QPainter *painter, const QRect &inner_rect) const
2375 {
2376  // Draw symbol as a parallelogram
2377  QPoint ctr = inner_rect.center();
2378  int r = inner_rect.height() * 7 / 20;
2379  painter->drawLine(ctr + QPoint(-r * 3 / 2, +r), ctr + QPoint(-r, -r));
2380  painter->drawLine(ctr + QPoint(-r, -r), ctr + QPoint(+r * 3 / 2, -r));
2381  painter->drawLine(ctr + QPoint(+r * 3 / 2, -r), ctr + QPoint(+r, +r));
2382  painter->drawLine(ctr + QPoint(+r, +r), ctr + QPoint(-r * 3 / 2, +r));
2383 }
2384 
2385 bool C4PropertyDelegatePolygon::IsPasteValid(const C4Value &val) const
2386 {
2387  // Expect just a vertex array
2388  return IsVertexPasteValid(val);
2389 }
2390 
2391 
2392 /* Delegate factory: Create delegates based on the C4Value type */
2393 
2394 C4PropertyDelegateFactory::C4PropertyDelegateFactory() : effect_delegate(this, nullptr)
2395 {
2396 
2397 }
2398 
2399 C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByPropList(C4PropList *props) const
2400 {
2401  if (props)
2402  {
2403  const C4String *str = props->GetPropertyStr(P_Type);
2404  if (str)
2405  {
2406  // create default base types
2407  if (str->GetData() == "int") return new C4PropertyDelegateInt(this, props);
2408  if (str->GetData() == "string") return new C4PropertyDelegateString(this, props);
2409  if (str->GetData() == "array") return new C4PropertyDelegateArray(this, props);
2410  if (str->GetData() == "proplist") return new C4PropertyDelegatePropList(this, props);
2411  if (str->GetData() == "color") return new C4PropertyDelegateColor(this, props);
2412  if (str->GetData() == "def") return new C4PropertyDelegateDef(this, props);
2413  if (str->GetData() == "object") return new C4PropertyDelegateObject(this, props);
2414  if (str->GetData() == "enum") return new C4PropertyDelegateEnum(this, props);
2415  if (str->GetData() == "sound") return new C4PropertyDelegateSound(this, props);
2416  if (str->GetData() == "bool") return new C4PropertyDelegateBool(this, props);
2417  if (str->GetData() == "has_effect") return new C4PropertyDelegateHasEffect(this, props);
2418  if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this, props);
2419  if (str->GetData() == "rect") return new C4PropertyDelegateRect(this, props);
2420  if (str->GetData() == "circle") return new C4PropertyDelegateCircle(this, props);
2421  if (str->GetData() == "point") return new C4PropertyDelegatePoint(this, props);
2422  if (str->GetData() == "graph") return new C4PropertyDelegateGraph(this, props);
2423  if (str->GetData() == "polyline") return new C4PropertyDelegatePolyline(this, props);
2424  if (str->GetData() == "polygon") return new C4PropertyDelegatePolygon(this, props);
2425  if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this, props);
2426  // unknown type
2427  LogF("Invalid delegate type: %s.", str->GetCStr());
2428  }
2429  }
2430  // Default fallback
2431  return new C4PropertyDelegateC4ValueInput(this, props);
2432 }
2433 
2434 C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByValue(const C4Value &val) const
2435 {
2436  auto iter = delegates.find(val.getPropList());
2437  if (iter != delegates.end()) return iter->second.get();
2438  C4PropertyDelegate *new_delegate = CreateDelegateByPropList(val.getPropList());
2439  delegates.insert(std::make_pair(val.getPropList(), std::unique_ptr<C4PropertyDelegate>(new_delegate)));
2440  return new_delegate;
2441 }
2442 
2443 C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByIndex(const QModelIndex &index) const
2444 {
2445  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2446  if (!prop) return nullptr;
2447  if (!prop->delegate) prop->delegate = GetDelegateByValue(prop->delegate_info);
2448  return prop->delegate;
2449 }
2450 
2451 void C4PropertyDelegateFactory::ClearDelegates()
2452 {
2453  delegates.clear();
2454 }
2455 
2456 void C4PropertyDelegateFactory::EditorValueChanged(QWidget *editor)
2457 {
2458  emit commitData(editor);
2459 }
2460 
2461 void C4PropertyDelegateFactory::EditingDone(QWidget *editor)
2462 {
2463  emit commitData(editor);
2464  //emit closeEditor(editor); - done by qt somewhere else...
2465 }
2466 
2467 void C4PropertyDelegateFactory::setEditorData(QWidget *editor, const QModelIndex &index) const
2468 {
2469  // Put property value from proplist into editor
2470  C4PropertyDelegate *d = GetDelegateByIndex(index);
2471  if (!CheckCurrentEditor(d, editor)) return;
2472  // Fetch property only first time - ignore further updates to the same value to simplify editing
2473  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2474  if (!prop) return;
2475  C4Value val;
2476  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2477  if (!prop->about_to_edit && val == last_edited_value) return;
2478  last_edited_value = val;
2479  prop->about_to_edit = false;
2480  d->SetEditorData(editor, val, d->GetPathForProperty(prop));
2481 }
2482 
2483 void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
2484 {
2485  // Fetch property value from editor and set it into proplist
2486  C4PropertyDelegate *d = GetDelegateByIndex(index);
2487  if (!CheckCurrentEditor(d, editor)) return;
2488  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2489  SetPropertyData(d, editor, prop);
2490 }
2491 
2492 void C4PropertyDelegateFactory::SetPropertyData(const C4PropertyDelegate *d, QObject *editor, C4ConsoleQtPropListModel::Property *editor_prop) const
2493 {
2494  // Set according to delegate
2495  const C4PropertyPath set_path = d->GetPathForProperty(editor_prop);
2496  d->SetModelData(editor, set_path, editor_prop->shape ? editor_prop->shape->Get() : nullptr);
2497 }
2498 
2499 QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
2500 {
2501  C4PropertyDelegate *d = GetDelegateByIndex(index);
2502  if (!d) return nullptr;
2503  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2504  prop->about_to_edit = true;
2505  QWidget *editor = d->CreateEditor(this, parent, option, true, false);
2506  // Connect value change signals (if editing is possible for this property)
2507  // For some reason, commitData needs a non-const pointer
2508  if (editor)
2509  {
2510  connect(d, &C4PropertyDelegate::EditorValueChangedSignal, editor, [editor, this](QWidget *signal_editor) {
2511  if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditorValueChanged(editor);
2512  });
2513  connect(d, &C4PropertyDelegate::EditingDoneSignal, editor, [editor, this](QWidget *signal_editor) {
2514  if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditingDone(editor);
2515  });
2516  }
2517  current_editor = editor;
2518  current_editor_delegate = d;
2519  return editor;
2520 }
2521 
2522 void C4PropertyDelegateFactory::destroyEditor(QWidget *editor, const QModelIndex &index) const
2523 {
2524  if (editor == current_editor)
2525  {
2526  current_editor = nullptr;
2527  current_editor_delegate = nullptr;
2529  }
2530  QStyledItemDelegate::destroyEditor(editor, index);
2531 }
2532 
2533 void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
2534 {
2535  C4PropertyDelegate *d = GetDelegateByIndex(index);
2536  if (!CheckCurrentEditor(d, editor)) return;
2537  return d->UpdateEditorGeometry(editor, option);
2538 }
2539 
2540 QSize C4PropertyDelegateFactory::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
2541 {
2542  int height = QApplication::fontMetrics().height() + 4;
2543  return QSize(100, height);
2544 }
2545 
2546 void C4PropertyDelegateFactory::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
2547 {
2548  // Delegate has custom painting?
2549  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2550  C4PropertyDelegate *d = GetDelegateByIndex(index);
2551  if (d && prop && d->HasCustomPaint())
2552  {
2553  C4Value val;
2554  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2555  if (d->Paint(painter, option, val)) return;
2556  }
2557  // Otherwise use default paint implementation
2558  QStyledItemDelegate::paint(painter, option, index);
2559 }
2560 
2561 void C4PropertyDelegateFactory::OnPropListChanged()
2562 {
2563  if (current_editor) emit closeEditor(current_editor);
2564 }
2565 
2566 bool C4PropertyDelegateFactory::CheckCurrentEditor(C4PropertyDelegate *d, QWidget *editor) const
2567 {
2568  if (!d || (editor && editor != current_editor) || d != current_editor_delegate)
2569  {
2570  //const_cast<C4PropertyDelegateFactory *>(this)->emit closeEditor(current_editor);
2571  destroyEditor(current_editor, QModelIndex());
2572  return false;
2573  }
2574  return true;
2575 }
2576 
2577 static const QString property_mime_type("application/OpenClonkProperty");
2578 
2579 void C4PropertyDelegateFactory::CopyToClipboard(const QModelIndex &index)
2580 {
2581  // Re-resolve property. May have shifted while the menu was open
2582  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2583  C4PropertyDelegate *d = GetDelegateByIndex(index);
2584  if (!prop || !d) return;
2585  // Get data to copy
2586  C4Value val;
2587  d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
2588  StdStrBuf data_str(val.GetDataString(99999));
2589  // Copy it as an internal mime type and text
2590  // Presence of the internal type shows that this is a copied property so it can be safely evaluate without sync problems
2591  QClipboard *clipboard = QApplication::clipboard();
2592  clipboard->clear();
2593  std::unique_ptr<QMimeData> data(new QMimeData());
2594  data->setData(property_mime_type, QByteArray(data_str.getData(), data_str.getSize()));
2595  data->setText(data_str.getData());
2596  clipboard->setMimeData(data.release());
2597 }
2598 
2599 bool C4PropertyDelegateFactory::PasteFromClipboard(const QModelIndex &index, bool check_only)
2600 {
2601  // Re-resolve property. May have shifted while the menu was open
2602  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2603  C4PropertyDelegate *d = GetDelegateByIndex(index);
2604  if (!prop || !d) return false;
2605  // Check value to paste
2606  QClipboard *clipboard = QApplication::clipboard();
2607  const QMimeData *data = clipboard->mimeData();
2608  if (!data) return false; // empty clipboard
2609  // Prefer copied property; fall back to text
2610  StdStrBuf str_data;
2611  if (data->hasFormat(property_mime_type))
2612  {
2613  QByteArray prop_data = data->data(property_mime_type);
2614  str_data.Copy(prop_data);
2615  // Check data type
2616  C4Value val = ::AulExec.DirectExec(&::ScriptEngine, str_data.getData(), "paste check", false, nullptr, false);
2617  if (!d->IsPasteValid(val)) return false;
2618  }
2619  else if (data->hasText())
2620  {
2621  // Text can always be pasted.
2622  // Cannot perform a type check here because a function may have been copied that affects sync.
2623  QString text = data->text();
2624  str_data.Copy(text.toUtf8());
2625  }
2626  else
2627  {
2628  // Unknown data type in clipboard. Cannot paste.
2629  return false;
2630  }
2631  if (check_only) return true;
2632  // Alright, paste!
2633  d->GetPathForProperty(prop).SetProperty(str_data.getData());
2634  return true;
2635 }
2636 
2637 bool C4PropertyDelegateFactory::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
2638 {
2639  // Custom context menu on item
2640  // I would like to use the regular context menu functions of Qt on the parent widget
2641  // but something is eating the right-click event before it triggers a context event.
2642  // So just hack it on right click.
2643  // Button check
2644  if (event->type() == QEvent::Type::MouseButtonPress)
2645  {
2646  QMouseEvent *mev = static_cast<QMouseEvent *>(event);
2647  if (mev->button() == Qt::MouseButton::RightButton)
2648  {
2649  // Item check
2650  C4ConsoleQtPropListModel::Property *prop = property_model->GetPropByIndex(index);
2651  C4PropertyDelegate *d = GetDelegateByIndex(index);
2652  if (d && prop)
2653  {
2654  // Context menu on a valid property: Show copy+paste menu
2655  QMenu *context = new QMenu(const_cast<QWidget *>(option.widget));
2656  QAction *copy_action = new QAction(LoadResStr("IDS_DLG_COPY"), context);
2657  QAction *paste_action = new QAction(LoadResStr("IDS_DLG_PASTE"), context);
2658  QModelIndex index_copy(index);
2659  connect(copy_action, &QAction::triggered, this, [this, index_copy]() {
2660  this->CopyToClipboard(index_copy);
2661  });
2662  connect(paste_action, &QAction::triggered, this, [this, index_copy]() {
2663  this->PasteFromClipboard(index_copy, false);
2664  });
2665  paste_action->setEnabled(PasteFromClipboard(index_copy, true)); // Paste grayed out if not valid
2666  context->addAction(copy_action);
2667  context->addAction(paste_action);
2668  context->popup(mev->globalPos());
2669  context->connect(context, &QMenu::aboutToHide, context, &QWidget::deleteLater);
2670  // It's easier to see which item is affected when it's selected
2671  QItemSelectionModel *sel_model = property_model->GetSelectionModel();
2672  QItemSelection new_sel;
2673  new_sel.select(model->index(index.row(), 0, index.parent()), index);
2674  sel_model->select(new_sel, QItemSelectionModel::SelectionFlag::SelectCurrent);
2675  return true;
2676  }
2677  }
2678  }
2679  return QStyledItemDelegate::editorEvent(event, model, option, index);
2680 }
2681 
2682 
2683 /* Proplist table view */
2684 
2685 C4ConsoleQtPropListModel::C4ConsoleQtPropListModel(C4PropertyDelegateFactory *delegate_factory)
2686  : delegate_factory(delegate_factory), selection_model(nullptr)
2687 {
2688  header_font.setBold(true);
2689  important_property_font.setBold(true);
2690  connect(this, &C4ConsoleQtPropListModel::ProplistChanged, this, &C4ConsoleQtPropListModel::UpdateSelection, Qt::QueuedConnection);
2691  layout_valid = false;
2692 }
2693 
2694 C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel() = default;
2695 
2696 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)
2697 {
2698  // Add all properties from this EditorProps group
2699  std::vector<C4String *> property_names = add_proplist->GetUnsortedProperties(nullptr);
2700  if (!property_names.size()) return false;
2701  // Prepare group array
2702  if (property_groups.size() == group_index)
2703  {
2704  layout_valid = false;
2705  property_groups.resize(group_index + 1);
2706  }
2707  PropertyGroup &properties = property_groups[group_index];
2708  // Resolve properties
2709  struct PropAndKey
2710  {
2711  C4PropList *prop;
2712  C4String *key;
2713  int32_t priority;
2714  C4String *name;
2715 
2716  PropAndKey(C4PropList *prop, C4String *key, int32_t priority, C4String *name)
2717  : prop(prop), key(key), priority(priority), name(name) {}
2718  };
2719  std::vector<PropAndKey> new_properties_resolved;
2720  new_properties_resolved.reserve(property_names.size());
2721  for (C4String *prop_name : property_names)
2722  {
2723  C4Value prop_val;
2724  add_proplist->GetPropertyByS(prop_name, &prop_val);
2725  C4PropList *prop = prop_val.getPropList();
2726  if (prop)
2727  {
2728  C4String *name = prop->GetPropertyStr(P_Name);
2729  if (!name) name = prop_name;
2730  int32_t priority = prop->GetPropertyInt(P_Priority);
2731  new_properties_resolved.emplace_back(PropAndKey({ prop, prop_name, priority, name }));
2732  }
2733  }
2734  // Sort by priority primarily and name secondarily
2735  std::sort(new_properties_resolved.begin(), new_properties_resolved.end(), [](const PropAndKey &a, const PropAndKey &b) -> bool {
2736  if (a.priority != b.priority) return a.priority > b.priority;
2737  return strcmp(a.name->GetCStr(), b.name->GetCStr()) < 0;
2738  });
2739  // Setup group
2740  properties.name = name;
2741  if (properties.props.size() != new_properties_resolved.size())
2742  {
2743  layout_valid = false;
2744  properties.props.resize(new_properties_resolved.size());
2745  }
2746  for (int32_t i = 0; i < new_properties_resolved.size(); ++i)
2747  {
2748  Property *prop = &properties.props[i];
2749  // Property access path
2750  prop->parent_value.SetPropList(target_proplist);
2751  prop->property_path = group_target_path;
2752  // ID for default selection memory
2753  const PropAndKey &prop_def = new_properties_resolved[i];
2754  if (default_selection == prop_def.key) *default_selection_index = i;
2755  // Property data
2756  prop->help_text = nullptr;
2757  prop->delegate_info.Set0(); // default C4Value delegate
2758  prop->group_idx = group_index;
2759  prop->about_to_edit = false;
2760  prop->key = prop_def.prop->GetPropertyStr(P_Key);
2761  if (!prop->key) properties.props[i].key = prop_def.key;
2762  prop->display_name = prop_def.name;
2763  if (!prop->display_name) prop->display_name = prop_def.key;
2764  prop->help_text = prop_def.prop->GetPropertyStr(P_EditorHelp);
2765  prop->priority = prop_def.priority;
2766  prop->delegate_info.SetPropList(prop_def.prop);
2767  prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
2768  C4Value v;
2769  C4Value v_target_proplist = C4VPropList(target_proplist);
2770  prop->delegate->GetPropertyValue(v_target_proplist, prop->key, 0, &v);
2771  // Connect editable shape to property
2772  C4PropertyPath new_shape_property_path = prop->delegate->GetPathForProperty(prop);
2773  const C4PropertyDelegateShape *new_shape_delegate = prop->delegate->GetShapeDelegate(v, &new_shape_property_path);
2774  if (new_shape_delegate != prop->shape_delegate || !(prop->shape_property_path == new_shape_property_path))
2775  {
2776  prop->shape_delegate = new_shape_delegate;
2777  prop->shape_property_path = new_shape_property_path;
2778  if (new_shape_delegate)
2779  {
2780  // Re-use loaded shape if possible (e.g. if only the index has moved)
2781  std::string shape_index = std::string(prop->shape_property_path.GetGetPath());
2782  prop->shape = &shapes[shape_index];
2783  C4ConsoleQtShape *shape = prop->shape->Get();
2784  if (shape)
2785  {
2786  if (shape->GetProperties() != new_shape_delegate->GetCreationProps().getPropList())
2787  {
2788  // Shape at same path but with different properties? Then re-create
2789  shape = nullptr;
2790  }
2791  }
2792  if (!shape)
2793  {
2794  // New shape or shape type mismatch: Generate new shape at this path and put into the shape holder list
2795  shape = ::Console.EditCursor.GetShapes()->CreateShape(base_object ? base_object : target_proplist->GetObject(), new_shape_delegate->GetCreationProps().getPropList(), v, new_shape_delegate);
2796  new_shape_delegate->ConnectSignals(shape, prop->shape_property_path);
2797  prop->shape->Set(shape);
2798  prop->shape->SetLastValue(v);
2799  }
2800  }
2801  else
2802  {
2803  prop->shape = nullptr;
2804  }
2805  }
2806  if (prop->shape)
2807  {
2808  // Mark this shape to be kept aftre update is complete
2809  prop->shape->visit();
2810  // Update shape by value if it was changed externally
2811  if (!prop->shape->GetLastValue().IsIdenticalTo(v))
2812  {
2813  prop->shape->Get()->SetValue(v);
2814  prop->shape->SetLastValue(v);
2815  }
2816  }
2817  }
2818  return true;
2819 }
2820 
2821 bool C4ConsoleQtPropListModel::AddEffectGroup(int32_t group_index, C4Object *base_object)
2822 {
2823  // Count non-dead effects
2824  C4Effect **effect_list = base_object ? &base_object->pEffects : &::ScriptEngine.pGlobalEffects;
2825  int32_t num_effects = 0;
2826  for (C4Effect *effect = *effect_list; effect; effect = effect->pNext)
2827  {
2828  num_effects += effect->IsActive();
2829  }
2830  // Return false to signal that no effect group has been added
2831  if (!num_effects) return false;
2832  // Prepare group array
2833  if (property_groups.size() == group_index)
2834  {
2835  layout_valid = false;
2836  property_groups.resize(group_index + 1);
2837  }
2838  PropertyGroup &properties = property_groups[group_index];
2839  if (properties.props.size() != num_effects)
2840  {
2841  layout_valid = false;
2842  properties.props.resize(num_effects);
2843  }
2844  properties.name = LoadResStr("IDS_CNS_EFFECTS");
2845  // Add all (non-dead) effects of given object (or global effects if base_object is nullptr)
2846  int32_t num_added = 0;
2847  for (C4Effect *effect = *effect_list; effect; effect = effect->pNext)
2848  {
2849  if (effect->IsActive())
2850  {
2851  Property *prop = &properties.props[num_added++];
2852  prop->parent_value.SetPropList(base_object ? (C4PropList *) base_object : &::ScriptEngine);
2853  prop->property_path = C4PropertyPath(effect, base_object);
2854  prop->help_text = nullptr;
2855  prop->delegate_info.Set0();
2856  prop->group_idx = group_index;
2857  prop->key = ::Strings.RegString(prop->property_path.GetGetPath());
2858  prop->display_name = effect->GetPropertyStr(P_Name);
2859  prop->priority = 0;
2860  prop->delegate = delegate_factory->GetEffectDelegate();
2861  prop->shape = nullptr;
2862  prop->shape_delegate = nullptr;
2863  prop->shape_property_path.Clear();
2864  prop->about_to_edit = false;
2865  prop->group_idx = group_index;
2866  }
2867  }
2868  // Return true to signal that effect group has been added
2869  return true;
2870 }
2871 
2872 void C4ConsoleQtPropListModel::SetBasePropList(C4PropList *new_proplist)
2873 {
2874  // Clear stack and select new proplist
2875  // Update properties
2876  target_value.SetPropList(new_proplist);
2877  base_proplist.SetPropList(new_proplist);
2878  // objects derive their custom properties
2879  info_proplist.SetPropList(target_value.getObj());
2880  target_path = C4PropertyPath(new_proplist);
2881  target_path_stack.clear();
2882  UpdateValue(true);
2883  delegate_factory->OnPropListChanged();
2884 }
2885 
2886 void C4ConsoleQtPropListModel::DescendPath(const C4Value &new_value, C4PropList *new_info_proplist, const C4PropertyPath &new_path)
2887 {
2888  // Add previous proplist to stack
2889  target_path_stack.emplace_back(target_path, target_value, info_proplist);
2890  // descend
2891  target_value = new_value;
2892  info_proplist.SetPropList(new_info_proplist);
2893  target_path = new_path;
2894  UpdateValue(true);
2895  delegate_factory->OnPropListChanged();
2896 }
2897 
2898 void C4ConsoleQtPropListModel::AscendPath()
2899 {
2900  // Go up in target stack (if possible)
2901  for (;;)
2902  {
2903  if (!target_path_stack.size())
2904  {
2905  SetBasePropList(nullptr);
2906  return;
2907  }
2908  TargetStackEntry entry = target_path_stack.back();
2909  target_path_stack.pop_back();
2910  if (!entry.value || !entry.info_proplist) continue; // property was removed; go up further in stack
2911  // Safety: Make sure we're still on the same value
2912  C4Value target = entry.path.ResolveValue();
2913  if (!target.IsIdenticalTo(entry.value)) continue;
2914  // Set new value
2915  target_path = entry.path;
2916  target_value = entry.value;
2917  info_proplist = entry.info_proplist;
2918  UpdateValue(true);
2919  break;
2920  }
2921  // Any current editor needs to close
2922  delegate_factory->OnPropListChanged();
2923 }
2924 
2925 void C4ConsoleQtPropListModel::UpdateValue(bool select_default)
2926 {
2927  emit layoutAboutToBeChanged();
2928  // Update target value from path
2929  target_value = target_path.ResolveValue();
2930  // Prepare shape list update
2931  C4ConsoleQtShapeHolder::begin_visit();
2932  // Safe-get from C4Values in case any prop lists or arrays got deleted
2933  int32_t num_groups, default_selection_group = -1, default_selection_index = -1;
2934  switch (target_value.GetType())
2935  {
2936  case C4V_PropList:
2937  num_groups = UpdateValuePropList(target_value._getPropList(), &default_selection_group, &default_selection_index);
2938  break;
2939  case C4V_Array:
2940  num_groups = UpdateValueArray(target_value._getArray(), &default_selection_group, &default_selection_index);
2941  break;
2942  default: // probably nil
2943  num_groups = 0;
2944  break;
2945  }
2946  // Remove any unreferenced shapes
2947  for (auto iter = shapes.begin(); iter != shapes.end(); )
2948  {
2949  if (!iter->second.was_visited())
2950  {
2951  iter = shapes.erase(iter);
2952  }
2953  else
2954  {
2955  ++iter;
2956  }
2957  }
2958  // Update model range
2959  if (num_groups != property_groups.size())
2960  {
2961  layout_valid = false;
2962  property_groups.resize(num_groups);
2963  }
2964  if (!layout_valid)
2965  {
2966  // We do not adjust persistent indices for now
2967  // 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
2968  layout_valid = true;
2969  }
2970  emit layoutChanged();
2971  QModelIndex topLeft = index(0, 0, QModelIndex());
2972  QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1, QModelIndex());
2973  emit dataChanged(topLeft, bottomRight);
2974  // Initial selection
2975  if (select_default) emit ProplistChanged(default_selection_group, default_selection_index);
2976 }
2977 
2978 void C4ConsoleQtPropListModel::UpdateSelection(int32_t major_sel, int32_t minor_sel) const
2979 {
2980  if (selection_model)
2981  {
2982  // Select by indexed elements only
2983  selection_model->clearSelection();
2984  if (major_sel >= 0)
2985  {
2986  QModelIndex sel = index(major_sel, 0, QModelIndex());
2987  if (minor_sel >= 0) sel = index(minor_sel, 0, sel);
2988  selection_model->select(sel, QItemSelectionModel::SelectCurrent);
2989  }
2990  else
2991  {
2992  selection_model->select(QModelIndex(), QItemSelectionModel::SelectCurrent);
2993  }
2994  }
2995 }
2996 
2997 int32_t C4ConsoleQtPropListModel::UpdateValuePropList(C4PropList *target_proplist, int32_t *default_selection_group, int32_t *default_selection_index)
2998 {
2999  assert(target_proplist);
3000  C4Object *base_obj = this->base_proplist.getObj(), *obj = nullptr;
3001  C4PropList *info_proplist = this->info_proplist.getPropList();
3002  int32_t num_groups = 0;
3003  // Selected shape properties
3004  C4ConsoleQtShape *selected_shape = ::Console.EditCursor.GetShapes()->GetSelectedShape();
3005  if (selected_shape)
3006  {
3007  // Find property information for this shape
3008  // Could also remember this pointer for every shape holder
3009  // - but that would have to be updated on any property group vector resize
3010  Property *prop = nullptr;
3011  for (PropertyGroup &grp : property_groups)
3012  {
3013  for (Property &check_prop : grp.props)
3014  {
3015  if (check_prop.shape && check_prop.shape->Get() == selected_shape)
3016  {
3017  prop = &check_prop;
3018  break;
3019  }
3020  }
3021  if (prop) break;
3022  }
3023  // Update selected shape item information
3024  if (prop && prop->delegate)
3025  {
3026  C4PropList *shape_item_editorprops, *shape_item_value;
3027  C4String *shape_item_name = nullptr;
3028  C4PropertyPath shape_item_target_path;
3029  C4Value v;
3030  C4Value v_target_proplist = C4VPropList(target_proplist);
3031  prop->delegate->GetPropertyValue(v_target_proplist, prop->key, 0, &v);
3032  C4PropertyPath shape_property_path = prop->delegate->GetPathForProperty(prop);
3033  prop->delegate->GetShapeDelegate(v, &shape_property_path); // to resolve v
3034  if (::Console.EditCursor.GetShapes()->GetSelectedShapeData(v, prop->shape_property_path, &shape_item_editorprops, &shape_item_value, &shape_item_name, &shape_item_target_path))
3035  {
3036  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))
3037  {
3038  ++num_groups;
3039  }
3040  }
3041  }
3042  }
3043  // Published properties
3044  if (info_proplist)
3045  {
3046  C4String *default_selection = info_proplist->GetPropertyStr(P_DefaultEditorProp);
3047  obj = info_proplist->GetObject();
3048  // Properties from effects (no inheritance supported)
3049  if (obj)
3050  {
3051  for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
3052  {
3053  if (!fx->IsActive()) continue; // skip dead effects
3054  QString name = fx->GetName();
3055  C4PropList *effect_editorprops = fx->GetPropertyPropList(P_EditorProps);
3056  if (effect_editorprops && AddPropertyGroup(effect_editorprops, num_groups, name, fx, C4PropertyPath(fx, obj), obj, nullptr, nullptr))
3057  ++num_groups;
3058  }
3059  }
3060  // Properties from object (but not on definition)
3061  if (obj || !info_proplist->GetDef())
3062  {
3063  C4PropList *info_editorprops = info_proplist->GetPropertyPropList(P_EditorProps);
3064  if (info_editorprops)
3065  {
3066  QString name = info_proplist->GetName();
3067  if (AddPropertyGroup(info_editorprops, num_groups, name, target_proplist, target_path, base_obj, default_selection, default_selection_index))
3068  ++num_groups;
3069  // Assign group for default selection
3070  if (*default_selection_index >= 0)
3071  {
3072  *default_selection_group = num_groups - 1;
3073  default_selection = nullptr; // don't find any other instances
3074  }
3075  }
3076  }
3077  // properties from global list for objects
3078  if (obj)
3079  {
3080  C4Def *editor_base = C4Id2Def(C4ID::EditorBase);
3081  C4PropList *info_editorprops = nullptr;
3082 
3083  if (editor_base && (info_editorprops = editor_base->GetPropertyPropList(P_EditorProps)))
3084  {
3085  if (AddPropertyGroup(info_editorprops, num_groups, LoadResStr("IDS_CNS_OBJECT"), target_proplist, target_path, base_obj, nullptr, nullptr))
3086  ++num_groups;
3087  }
3088  }
3089  }
3090  // Always: Internal properties
3091  auto new_properties = target_proplist->GetSortedLocalProperties();
3092  if (property_groups.size() == num_groups) property_groups.resize(num_groups + 1);
3093  PropertyGroup &internal_properties = property_groups[num_groups];
3094  internal_properties.name = LoadResStr("IDS_CNS_INTERNAL");
3095  internal_properties.props.resize(new_properties.size());
3096  for (int32_t i = 0; i < new_properties.size(); ++i)
3097  {
3098  internal_properties.props[i].parent_value = this->target_value;
3099  internal_properties.props[i].property_path = target_path;
3100  internal_properties.props[i].key = new_properties[i];
3101  internal_properties.props[i].display_name = new_properties[i];
3102  internal_properties.props[i].help_text = nullptr;
3103  internal_properties.props[i].priority = 0;
3104  internal_properties.props[i].delegate_info.Set0(); // default C4Value delegate
3105  internal_properties.props[i].delegate = nullptr; // init when needed
3106  internal_properties.props[i].group_idx = num_groups;
3107  internal_properties.props[i].shape = nullptr;
3108  internal_properties.props[i].shape_property_path.Clear();
3109  internal_properties.props[i].shape_delegate = nullptr;
3110  internal_properties.props[i].about_to_edit = false;
3111  }
3112  ++num_groups;
3113  // Effects
3114  // Add after internal because the gorup may be added/removed quickly
3115  if (obj)
3116  {
3117  // Object: Show object effects
3118  if (AddEffectGroup(num_groups, obj))
3119  {
3120  ++num_groups;
3121  }
3122  }
3123  else if (target_proplist == &::ScriptEngine)
3124  {
3125  // Global object: Show global effects
3126  if (AddEffectGroup(num_groups, nullptr))
3127  {
3128  ++num_groups;
3129  }
3130  }
3131  return num_groups;
3132 }
3133 
3134 int32_t C4ConsoleQtPropListModel::UpdateValueArray(C4ValueArray *target_array, int32_t *default_selection_group, int32_t *default_selection_index)
3135 {
3136  if (property_groups.empty())
3137  {
3138  layout_valid = false;
3139  property_groups.resize(1);
3140  }
3141  C4PropList *info_proplist = this->info_proplist.getPropList();
3142  C4Value elements_delegate_value;
3143  if (info_proplist) info_proplist->GetProperty(P_Elements, &elements_delegate_value);
3144  property_groups[0].name = (info_proplist ? info_proplist->GetName() : LoadResStr("IDS_CNS_ARRAYEDIT"));
3145  PropertyGroup &properties = property_groups[0];
3146  if (properties.props.size() != target_array->GetSize())
3147  {
3148  layout_valid = false;
3149  properties.props.resize(target_array->GetSize());
3150  }
3151  C4PropertyDelegate *item_delegate = delegate_factory->GetDelegateByValue(elements_delegate_value);
3152  for (int32_t i = 0; i < properties.props.size(); ++i)
3153  {
3154  Property &prop = properties.props[i];
3155  prop.property_path = C4PropertyPath(target_path, i);
3156  prop.parent_value = target_value;
3157  prop.display_name = ::Strings.RegString(FormatString("%d", (int)i).getData());
3158  prop.help_text = nullptr;
3159  prop.key = nullptr;
3160  prop.priority = 0;
3161  prop.delegate_info = elements_delegate_value;
3162  prop.delegate = item_delegate;
3163  prop.about_to_edit = false;
3164  prop.group_idx = 0;
3165  prop.shape = nullptr; // array elements cannot have shapes
3166  prop.shape_property_path.Clear();
3167  prop.shape_delegate = nullptr;
3168  }
3169  return 1; // one group for the values
3170 }
3171 
3172 void C4ConsoleQtPropListModel::DoOnUpdateCall(const C4PropertyPath &updated_path, const C4PropertyDelegate *delegate)
3173 {
3174  // If delegate has its own update clalback, perform that on the root
3175  const char *update_callback = delegate->GetUpdateCallback();
3176  if (update_callback)
3177  {
3178  ::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(FormatString("%s->%s(%s)", updated_path.GetRoot(), update_callback, updated_path.GetGetPath()).getData(), 0, false));
3179  }
3180  // Do general object property update control
3181  C4PropList *base_proplist = this->base_proplist.getPropList();
3182  C4Value q;
3183  if (base_proplist && base_proplist->GetProperty(P_EditorPropertyChanged, &q))
3184  {
3185  ::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));
3186  }
3187 }
3188 
3189 C4ConsoleQtPropListModel::Property *C4ConsoleQtPropListModel::GetPropByIndex(const QModelIndex &index) const
3190 {
3191  if (!index.isValid()) return nullptr;
3192  // Resolve group and row
3193  int32_t group_index = index.internalId(), row = index.row();
3194  // Prop list access: Properties are on 2nd level
3195  if (!group_index) return nullptr;
3196  --group_index;
3197  if (group_index >= property_groups.size()) return nullptr;
3198  if (row < 0 || row >= property_groups[group_index].props.size()) return nullptr;
3199  return const_cast<Property *>(&property_groups[group_index].props[row]);
3200 }
3201 
3202 int C4ConsoleQtPropListModel::rowCount(const QModelIndex & parent) const
3203 {
3204  QModelIndex grandparent;
3205  // Top level: Property groups
3206  if (!parent.isValid())
3207  {
3208  return property_groups.size();
3209  }
3210  // Mid level: Descend into property lists
3211  grandparent = parent.parent();
3212  if (!grandparent.isValid())
3213  {
3214  if (parent.row() >= 0 && parent.row() < property_groups.size())
3215  return property_groups[parent.row()].props.size();
3216  }
3217  return 0; // no 3rd level depth
3218 }
3219 
3220 int C4ConsoleQtPropListModel::columnCount(const QModelIndex & parent) const
3221 {
3222  return 2; // Name + Data (or Index + Data)
3223 }
3224 
3225 QVariant C4ConsoleQtPropListModel::headerData(int section, Qt::Orientation orientation, int role) const
3226 {
3227  // Table headers
3228  if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal)
3229  {
3230  if (section == 0)
3231  if (target_value.GetType() == C4V_Array)
3232  return QVariant(LoadResStr("IDS_CNS_INDEXSHORT"));
3233  else
3234  return QVariant(LoadResStr("IDS_CTL_NAME"));
3235  if (section == 1) return QVariant(LoadResStr("IDS_CNS_VALUE"));
3236  }
3237  return QVariant();
3238 }
3239 
3240 QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) const
3241 {
3242  // Headers
3243  int32_t group_index = index.internalId();
3244  if (!group_index)
3245  {
3246  if (!index.column())
3247  {
3248  if (role == Qt::DisplayRole)
3249  {
3250  if (index.row() >= 0 && index.row() < property_groups.size())
3251  return property_groups[index.row()].name;
3252  }
3253  else if (role == Qt::FontRole)
3254  {
3255  return header_font;
3256  }
3257  }
3258  return QVariant();
3259  }
3260  // Query latest data from prop list
3261  Property *prop = GetPropByIndex(index);
3262  if (!prop) return QVariant();
3263  if (!prop->delegate) prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
3264  if (role == Qt::DisplayRole)
3265  {
3266  switch (index.column())
3267  {
3268  case 0: // First col: Property Name
3269  return QVariant(prop->display_name->GetCStr());
3270  case 1: // Second col: Property value
3271  {
3272  C4Value v;
3273  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3274  return QVariant(prop->delegate->GetDisplayString(v, target_value.getObj(), true));
3275  }
3276  }
3277  }
3278  else if (role == Qt::BackgroundColorRole && index.column()==1)
3279  {
3280  C4Value v;
3281  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3282  QColor bgclr = prop->delegate->GetDisplayBackgroundColor(v, target_value.getObj());
3283  if (bgclr.isValid()) return bgclr;
3284  }
3285  else if (role == Qt::TextColorRole && index.column() == 1)
3286  {
3287  C4Value v;
3288  prop->delegate->GetPropertyValue(prop->parent_value, prop->key, index.row(), &v);
3289  QColor txtclr = prop->delegate->GetDisplayTextColor(v, target_value.getObj());
3290  if (txtclr.isValid()) return txtclr;
3291  }
3292  else if (role == Qt::DecorationRole && index.column() == 0 && prop->help_text && Config.Developer.ShowHelp)
3293  {
3294  // Help icons in left column
3295  return QIcon(":/editor/res/Help.png");
3296  }
3297  else if (role == Qt::FontRole && index.column() == 0)
3298  {
3299  if (prop->priority >= 100) return important_property_font;
3300  }
3301  else if (role == Qt::ToolTipRole && index.column() == 0)
3302  {
3303  // Tooltip from property description. Default to display name in case it got truncated.
3304  if (prop->help_text)
3305  return QString(prop->help_text->GetCStr());
3306  else
3307  return QString(prop->display_name->GetCStr());
3308  }
3309  // Nothing to show
3310  return QVariant();
3311 }
3312 
3313 QModelIndex C4ConsoleQtPropListModel::index(int row, int column, const QModelIndex &parent) const
3314 {
3315  if (column < 0 || column > 1) return QModelIndex();
3316  // Top level index?
3317  if (!parent.isValid())
3318  {
3319  // Top level has headers only
3320  if (row < 0 || row >= property_groups.size()) return QModelIndex();
3321  return createIndex(row, column, (quintptr)0u);
3322  }
3323  if (parent.internalId()) return QModelIndex(); // No 3rd level depth
3324  // Validate range of property
3325  const PropertyGroup *property_group = nullptr;
3326  if (parent.row() >= 0 && parent.row() < property_groups.size())
3327  {
3328  property_group = &property_groups[parent.row()];
3329  if (row < 0 || row >= property_group->props.size()) return QModelIndex();
3330  return createIndex(row, column, (quintptr)parent.row()+1);
3331  }
3332  return QModelIndex();
3333 }
3334 
3335 QModelIndex C4ConsoleQtPropListModel::parent(const QModelIndex &index) const
3336 {
3337  // Parent: Stored in internal ID
3338  auto parent_idx = index.internalId();
3339  if (parent_idx) return createIndex(parent_idx - 1, 0, (quintptr)0u);
3340  return QModelIndex();
3341 }
3342 
3343 Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const
3344 {
3345  Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;
3346  Property *prop = GetPropByIndex(index);
3347  if (index.isValid() && prop)
3348  {
3349  flags &= ~Qt::ItemIsDropEnabled; // only drop between the lines
3350  if (index.column() == 0)
3351  {
3352  // array elements can be re-arranged
3353  if (prop->parent_value.GetType() == C4V_Array) flags |= Qt::ItemIsDragEnabled;
3354  }
3355  else if (index.column() == 1)
3356  {
3357  // Disallow editing on readonly target (e.g. frozen proplist).
3358  // But always allow editing of effects.
3359  bool readonly = IsTargetReadonly() && prop->delegate != delegate_factory->GetEffectDelegate();
3360  if (!readonly)
3361  flags |= Qt::ItemIsEditable;
3362  else
3363  flags &= ~Qt::ItemIsEnabled;
3364  }
3365  }
3366  return flags;
3367 }
3368 
3369 Qt::DropActions C4ConsoleQtPropListModel::supportedDropActions() const
3370 {
3371  return Qt::MoveAction;
3372 }
3373 
3374 bool C4ConsoleQtPropListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
3375 {
3376  // Drag+Drop movement on array only
3377  if (action != Qt::MoveAction) return false;
3378  C4ValueArray *arr = target_value.getArray();
3379  if (!arr) return false;
3380  if (!data->hasFormat("application/vnd.text")) return false;
3381  if (row < 0) return false; // outside range: Could be above or below. Better don't drag at all.
3382  if (!parent.isValid()) return false; // in array only
3383  // Decode indices of rows to move
3384  QByteArray encodedData = data->data("application/vnd.text");
3385  StdStrBuf rearrange_call;
3386  rearrange_call.Format("MoveArrayItems(%%s, [%s], %d)", encodedData.constData(), row);
3387  target_path.DoCall(rearrange_call.getData());
3388  return true;
3389 }
3390 
3391 QStringList C4ConsoleQtPropListModel::mimeTypes() const
3392 {
3393  QStringList types;
3394  types << "application/vnd.text";
3395  return types;
3396 }
3397 
3398 QMimeData *C4ConsoleQtPropListModel::mimeData(const QModelIndexList &indexes) const
3399 {
3400  // Add all moved indexes
3401  QMimeData *mimeData = new QMimeData();
3402  QByteArray encodedData;
3403  int32_t count = 0;
3404  for (const QModelIndex &index : indexes)
3405  {
3406  if (index.isValid() && index.internalId())
3407  {
3408  if (count) encodedData.append(",");
3409  encodedData.append(QString::number(index.row()));
3410  ++count;
3411  }
3412  }
3413  mimeData->setData("application/vnd.text", encodedData);
3414  return mimeData;
3415 }
3416 
3417 QString C4ConsoleQtPropListModel::GetTargetPathHelp() const
3418 {
3419  // Help text in EditorInfo prop. Fall back to description.
3420  C4PropList *info_proplist = this->info_proplist.getPropList();
3421  if (!info_proplist) return QString();
3422  C4String *desc = info_proplist->GetPropertyStr(P_EditorHelp);
3423  if (!desc) desc = info_proplist->GetPropertyStr(P_Description);
3424  if (!desc) return QString();
3425  QString result = QString(desc->GetCStr());
3426  result = result.replace('|', '\n');
3427  return result;
3428 }
3429 
3430 const char *C4ConsoleQtPropListModel::GetTargetPathName() const
3431 {
3432  // Name prop of current info.
3433  C4PropList *info_proplist = this->info_proplist.getPropList();
3434  if (!info_proplist) return nullptr;
3435  C4String *name = info_proplist->GetPropertyStr(P_Name);
3436  return name ? name->GetCStr() : nullptr;
3437 }
3438 
3439 void C4ConsoleQtPropListModel::AddArrayElement()
3440 {
3441  C4Value new_val;
3442  C4PropList *info_proplist = this->info_proplist.getPropList();
3443  C4PropListStatic *info_proplist_static = nullptr;
3444  if (info_proplist)
3445  {
3446  info_proplist->GetProperty(P_DefaultValue, &new_val);
3447  info_proplist_static = info_proplist->IsStatic();
3448  }
3449  target_path.DoCall(FormatString("PushBack(%%s, %s)", new_val.GetDataString(10, info_proplist_static).getData()).getData());
3450 }
3451 
3452 void C4ConsoleQtPropListModel::RemoveArrayElement()
3453 {
3454  // Compose script command to remove all selected array indices
3455  StdStrBuf script;
3456  for (QModelIndex idx : selection_model->selectedIndexes())
3457  if (idx.isValid() && idx.column() == 0)
3458  if (script.getLength())
3459  script.AppendFormat(",%d", idx.row());
3460  else
3461  script.AppendFormat("%d", idx.row());
3462  if (script.getLength()) target_path.DoCall(FormatString("RemoveArrayIndices(%%s, [%s])", script.getData()).getData());
3463 }
3464 
3465 bool C4ConsoleQtPropListModel::IsTargetReadonly() const
3466 {
3467  if (target_path.IsEmpty()) return true;
3468  switch (target_value.GetType())
3469  {
3470  case C4V_Array:
3471  // Arrays are never frozen
3472  return false;
3473  case C4V_PropList:
3474  {
3475  C4PropList *parent_proplist = target_value._getPropList();
3476  if (parent_proplist->IsFrozen()) return true;
3477  return false;
3478  }
3479  default:
3480  return true;
3481  }
3482 }
3483 
3484 class C4ConsoleQtShape *C4ConsoleQtPropListModel::GetShapeByPropertyPath(const char *property_path)
3485 {
3486  // Lookup in map
3487  auto entry = shapes.find(std::string(property_path));
3488  if (entry == shapes.end()) return nullptr;
3489  return entry->second.Get();
3490 }
C4AulExec AulExec
Definition: C4AulExec.cpp:29
#define s
C4Config Config
Definition: C4Config.cpp:930
uint32_t GetTextColorForBackground(uint32_t background_color)
C4Def * C4Id2Def(C4ID id)
Definition: C4DefList.h:84
C4GameControl Control
@ CDT_Decide
Definition: C4GameControl.h:39
C4Game Game
Definition: C4Globals.cpp:52
C4AulScriptEngine ScriptEngine
Definition: C4Globals.cpp:43
C4Console Console
Definition: C4Globals.cpp:45
C4Application Application
Definition: C4Globals.cpp:44
C4GameObjects Objects
Definition: C4Globals.cpp:48
C4DefList Definitions
Definition: C4Globals.cpp:49
C4StringTable Strings
Definition: C4Globals.cpp:42
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
#define a
#define b
@ CID_EditGraph
Definition: C4PacketBase.h:172
@ CID_Script
Definition: C4PacketBase.h:154
C4SoundInstance * StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
C4PropertyName
@ P_Translatable
@ P_Display
@ P_EditorPropertyChanged
@ P_EditorHelp
@ P_DefaultEditorProp
@ P_Proplist
@ P_proplist
@ P_DescendPath
@ P_Step
@ P_Name
@ P_Priority
@ P_Group
@ P_Color
@ P_Wdt
@ P_Min
@ P_Value
@ P_Max
@ P_EditorProps
@ P_Set
@ P_Vertices
@ P_StructureFix
@ P_EditOnSelection
@ P_HorizontalFix
@ P_DefaultValueFunction
@ P_Effect
@ P_Elements
@ P_Y
@ P_ShortName
@ P_EmptyName
@ P_CanMoveCenter
@ P_Prototype
@ P_Hgt
@ P_wdt
@ P_ForceSerialization
@ P_OptionKey
@ P_hgt
@ P_Translate
@ P_OnUpdate
@ P_Filter
@ P_Key
@ P_Edges
@ P_Type
@ P_Sorted
@ P_X
@ P_ValueKey
@ P_x
@ P_Delegate
@ P_VerticalFix
@ P_SetGlobal
@ P_Description
@ P_Storage
@ P_AllowEditing
@ P_DefaultValue
@ P_Function
@ P_AsyncGet
@ P_Options
@ P_y
@ P_Alpha
@ P_SetRoot
C4Value C4VObj(C4Object *pObj)
Definition: C4Value.cpp:88
const C4Value C4VNull
Definition: C4Value.cpp:30
C4Value C4VBool(bool b)
Definition: C4Value.h:240
C4V_Type
Definition: C4Value.h:24
@ C4V_Function
Definition: C4Value.h:31
@ C4V_Int
Definition: C4Value.h:26
@ C4V_PropList
Definition: C4Value.h:28
@ C4V_Object
Definition: C4Value.h:38
@ C4V_Any
Definition: C4Value.h:37
@ C4V_Bool
Definition: C4Value.h:27
@ C4V_Def
Definition: C4Value.h:39
@ C4V_Effect
Definition: C4Value.h:40
@ C4V_Array
Definition: C4Value.h:30
@ C4V_Nil
Definition: C4Value.h:25
@ C4V_String
Definition: C4Value.h:29
C4Value C4VInt(int32_t i)
Definition: C4Value.h:239
C4Value C4VPropList(C4PropList *p)
Definition: C4Value.h:242
C4Value C4VString(C4String *pStr)
Definition: C4Value.h:243
const char * SSearch(const char *szString, const char *szIndex)
Definition: Standard.cpp:369
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
void RemoveExtension(char *szFilename)
Definition: StdFile.cpp:303
C4SoundSystem SoundSystem
Definition: C4Application.h:42
C4Value DirectExec(C4PropList *p, const char *szScript, const char *szContext, bool fPassErrors=false, C4AulScriptContext *context=nullptr, bool parse_function=false)
Definition: C4AulExec.cpp:1012
C4PropListStatic * GetPropList()
Definition: C4Aul.h:151
C4Effect * pGlobalEffects
Definition: C4Aul.h:144
char LanguageEx[CFG_MaxString+1]
Definition: C4Config.h:38
C4ConfigGeneral General
Definition: C4Config.h:255
C4ConfigDeveloper Developer
Definition: C4Config.h:256
C4EditCursor EditCursor
Definition: C4Console.h:90
Definition: C4Def.h:99
std::vector< C4Def * > GetAllDefs(C4String *filter_property=nullptr) const
Definition: C4DefList.cpp:304
void EMControl(enum C4PacketType eCtrlType, class C4ControlPacket *pCtrl)
bool GetCurrentSelectionPosition(int32_t *x, int32_t *y)
void SetHighlightedObject(C4Object *new_highlight)
void InvalidateSelection()
Definition: C4EditCursor.h:152
bool IsActive()
Definition: C4Effect.h:112
C4Effect * pNext
Definition: C4Effect.h:75
int32_t iTime
Definition: C4Effect.h:73
int32_t iInterval
Definition: C4Effect.h:73
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
C4String * GetTranslatedString(const class C4Value &input_string, C4Value *selected_language, bool fail_silently) const
Definition: C4Game.cpp:4810
C4PropList * AllocateTranslatedString()
Definition: C4Game.cpp:4883
C4Object * SafeObjectPointer(int32_t object_number)
static const C4ID EditorBase
Definition: C4Id.h:42
bool GetPropertyByS(const C4String *k, C4Value *pResult) const override
Definition: C4Object.cpp:1342
C4Effect * pEffects
Definition: C4Object.h:155
int32_t GetX() const
Definition: C4Object.h:285
int32_t GetY() const
Definition: C4Object.h:286
C4ObjectPtr Contained
Definition: C4Object.h:142
bool IsFrozen() const
Definition: C4PropList.h:135
int32_t GetPropertyInt(C4PropertyName k, int32_t default_val=0) const
Definition: C4PropList.cpp:855
int32_t GetPropertyBool(C4PropertyName n, bool default_val=false) const
Definition: C4PropList.cpp:841
virtual C4Object * GetObject()
Definition: C4PropList.cpp:636
virtual const char * GetName() const
Definition: C4PropList.cpp:618
virtual class C4PropListStatic * IsStatic()
Definition: C4PropList.h:89
int32_t Status
Definition: C4PropList.h:173
virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const
Definition: C4PropList.cpp:726
C4ValueArray * GetPropertyArray(C4PropertyName n) const
Definition: C4PropList.cpp:758
virtual C4Effect * GetEffect()
Definition: C4PropList.cpp:678
std::vector< C4String * > GetSortedLocalProperties(bool add_prototype=true) const
Definition: C4PropList.cpp:545
C4PropList * GetPropertyPropList(C4PropertyName k) const
Definition: C4PropList.cpp:869
C4String * GetPropertyStr(C4PropertyName k) const
Definition: C4PropList.cpp:744
virtual C4Def const * GetDef() const
Definition: C4PropList.cpp:654
C4Value Call(C4PropertyName k, C4AulParSet *pPars=nullptr, bool fPassErrors=false)
Definition: C4PropList.h:114
virtual void SetPropertyByS(C4String *k, const C4Value &to)
Definition: C4PropList.cpp:940
bool GetProperty(C4PropertyName k, C4Value *pResult) const
Definition: C4PropList.h:105
std::vector< C4String * > GetUnsortedProperties(const char *prefix, C4PropList *ignore_parent=nullptr) const
Definition: C4PropList.cpp:583
const C4PropListStatic * GetParent() const
Definition: C4PropList.h:275
void SetProperty(const char *set_string) const
const char * GetRoot() const
const char * GetGetPath() const
void SetSetPath(const C4PropertyPath &parent, const char *child_property, PathType path_type)
T * Get() const
C4SoundEffect * Next
C4SoundEffect * GetFirstSound() const
Definition: C4SoundSystem.h:52
StdStrBuf GetData() const
Definition: C4StringTable.h:50
const char * GetCStr() const
Definition: C4StringTable.h:49
C4String P[P_LAST]
C4String * RegString(StdStrBuf String)
const C4Value & GetItem(int32_t iElem) const
Definition: C4ValueArray.h:38
int32_t GetSize() const
Definition: C4ValueArray.h:36
C4ValueArray * getArray() const
Definition: C4Value.h:118
C4V_Type GetTypeEx() const
Definition: C4Value.cpp:623
C4Object * getObj() const
Definition: C4Value.cpp:68
int32_t getInt() const
Definition: C4Value.h:112
StdStrBuf GetDataString(int depth=10, const class C4PropListStatic *ignore_reference_parent=nullptr) const
Definition: C4Value.cpp:131
C4PropList * _getPropList() const
Definition: C4Value.h:129
C4String * getStr() const
Definition: C4Value.h:117
C4V_Type GetType() const
Definition: C4Value.h:161
void Set0()
Definition: C4Value.h:332
int32_t _getInt() const
Definition: C4Value.h:122
C4ValueArray * _getArray() const
Definition: C4Value.h:127
bool IsIdenticalTo(const C4Value &cmp) const
Definition: C4Value.h:149
C4PropList * getPropList() const
Definition: C4Value.h:116
C4Def * getDef() const
Definition: C4Value.cpp:78
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:190
const char * getData() const
Definition: StdBuf.h:442
void AppendChar(char cChar)
Definition: StdBuf.h:588
void Copy()
Definition: StdBuf.h:467
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
size_t getLength() const
Definition: StdBuf.h:445
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
void MouseMove(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam, class C4Viewport *pVP)
Definition: C4Gui.h:2832
void Right(T &keys)