2016-02-12 04:37:13 +00:00
|
|
|
/*
|
|
|
|
* OpenClonk, http://www.openclonk.org
|
|
|
|
*
|
|
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
|
|
* Copyright (c) 2013, The OpenClonk Team and contributors
|
|
|
|
*
|
|
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
|
|
* "COPYING" for details.
|
|
|
|
*
|
|
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
|
|
* See accompanying file "TRADEMARK" for details.
|
|
|
|
*
|
|
|
|
* To redistribute this file separately, substitute the full license texts
|
|
|
|
* for the above references.
|
|
|
|
*/
|
|
|
|
|
2016-04-03 19:06:32 +00:00
|
|
|
#include "C4Include.h"
|
|
|
|
#include "script/C4Value.h"
|
|
|
|
#include "editor/C4ConsoleQtPropListViewer.h"
|
2016-06-16 01:57:59 +00:00
|
|
|
#include "editor/C4ConsoleQtDefinitionListViewer.h"
|
2016-04-25 20:17:10 +00:00
|
|
|
#include "editor/C4ConsoleQtState.h"
|
2016-04-04 23:34:02 +00:00
|
|
|
#include "editor/C4Console.h"
|
|
|
|
#include "object/C4Object.h"
|
2016-04-09 18:20:31 +00:00
|
|
|
#include "object/C4DefList.h"
|
|
|
|
#include "object/C4Def.h"
|
2016-04-17 02:02:24 +00:00
|
|
|
#include "script/C4Effect.h"
|
2016-05-25 03:06:00 +00:00
|
|
|
#include "script/C4AulExec.h"
|
2016-04-04 03:46:58 +00:00
|
|
|
|
|
|
|
/* Property path for property setting synchronization */
|
|
|
|
|
2016-05-23 23:22:05 +00:00
|
|
|
C4PropertyPath::C4PropertyPath(C4PropList *target)
|
|
|
|
{
|
|
|
|
// Build string to set target: supports objects only
|
|
|
|
if (target)
|
|
|
|
{
|
|
|
|
C4Object *obj = target->GetObject();
|
|
|
|
if (obj) path.Format("Object(%d)", (int)obj->Number);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyPath::C4PropertyPath(C4Effect *fx, C4Object *target_obj)
|
|
|
|
{
|
|
|
|
// Effect property path: Represent as GetEffect("name", Object(%d), index)
|
|
|
|
if (!fx || !target_obj) return;
|
|
|
|
const char *name = fx->GetName();
|
|
|
|
int32_t index = 0;
|
|
|
|
for (C4Effect *ofx = target_obj->pEffects; ofx; ofx = ofx->pNext)
|
|
|
|
if (ofx == fx) break; else if (!strcmp(ofx->GetName(), name)) ++index;
|
|
|
|
path.Format("GetEffect(\"%s\", Object(%d), %d)", name, (int)target_obj->Number, (int)index);
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
C4PropertyPath::C4PropertyPath(const C4PropertyPath &parent, int32_t elem_index)
|
|
|
|
{
|
|
|
|
path.Format("%s[%d]", parent.GetPath(), (int)elem_index);
|
|
|
|
path_type = PPT_Index;
|
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyPath::C4PropertyPath(const C4PropertyPath &parent, const char *child_property, C4PropertyPath::PathType path_type)
|
|
|
|
: path_type(path_type)
|
|
|
|
{
|
|
|
|
if (path_type == PPT_Property)
|
|
|
|
path.Format("%s.%s", parent.GetPath(), child_property);
|
|
|
|
else if (path_type == PPT_SetFunction)
|
|
|
|
path.Format("%s->%s", parent.GetPath(), child_property);
|
2016-05-23 23:22:05 +00:00
|
|
|
else if (path_type == PPT_GlobalSetFunction)
|
|
|
|
{
|
|
|
|
path.Copy(parent.GetPath());
|
|
|
|
argument.Copy(child_property);
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyPath::SetProperty(const char *set_string) const
|
|
|
|
{
|
|
|
|
// Compose script to update property
|
|
|
|
StdStrBuf script;
|
2016-05-23 23:22:05 +00:00
|
|
|
if (path_type == PPT_SetFunction)
|
2016-04-04 03:46:58 +00:00
|
|
|
script.Format("%s(%s)", path.getData(), set_string);
|
2016-05-23 23:22:05 +00:00
|
|
|
else if (path_type == PPT_GlobalSetFunction)
|
|
|
|
script.Format("%s(%s,%s)", argument.getData(), path.getData(), set_string);
|
|
|
|
else
|
|
|
|
script.Format("%s=%s", path.getData(), set_string);
|
2016-04-04 03:46:58 +00:00
|
|
|
// Execute synced scripted
|
2016-05-23 23:22:05 +00:00
|
|
|
::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(script.getData(), 0, false));
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyPath::SetProperty(const C4Value &to_val) const
|
|
|
|
{
|
|
|
|
SetProperty(to_val.GetDataString(9999999).getData());
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
C4Value C4PropertyPath::ResolveValue() const
|
|
|
|
{
|
2016-06-07 03:26:20 +00:00
|
|
|
if (!path.getLength()) return C4VNull;
|
2016-05-25 03:06:00 +00:00
|
|
|
return AulExec.DirectExec(::ScriptEngine.GetPropList(), path.getData(), "resolve property", false, nullptr);
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
void C4PropertyPath::DoCall(const char *call_string) const
|
|
|
|
{
|
|
|
|
// Compose script call
|
|
|
|
StdStrBuf script;
|
|
|
|
script.Format(call_string, path.getData());
|
|
|
|
// Execute synced scripted
|
|
|
|
::Console.EditCursor.EMControl(CID_Script, new C4ControlScript(script.getData(), 0, false));
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
|
|
|
|
/* Property editing */
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegate::C4PropertyDelegate(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
2016-05-23 23:22:05 +00:00
|
|
|
: QObject(), factory(factory), set_function_is_global(false)
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-04-17 02:02:24 +00:00
|
|
|
// Resolve getter+setter callback names
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
set_function = props->GetPropertyStr(P_Set);
|
2016-05-23 23:22:05 +00:00
|
|
|
set_function_is_global = props->GetPropertyBool(P_Global);
|
2016-04-17 02:02:24 +00:00
|
|
|
async_get_function = props->GetPropertyStr(P_AsyncGet);
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
void C4PropertyDelegate::UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const
|
|
|
|
{
|
|
|
|
editor->setGeometry(option.rect);
|
|
|
|
}
|
|
|
|
|
2016-05-28 13:30:54 +00:00
|
|
|
bool C4PropertyDelegate::GetPropertyValueBase(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
switch (container.GetType())
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
case C4V_PropList:
|
|
|
|
return container._getPropList()->GetPropertyByS(key, out_val);
|
|
|
|
case C4V_Array:
|
|
|
|
*out_val = container._getArray()->GetItem(index);
|
2016-04-17 02:02:24 +00:00
|
|
|
return true;
|
2016-05-28 13:30:54 +00:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool C4PropertyDelegate::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
|
|
|
|
{
|
|
|
|
if (async_get_function)
|
|
|
|
{
|
|
|
|
C4PropList *props = container.getPropList();
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
*out_val = props->Call(async_get_function.Get());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
return GetPropertyValueBase(container, key, index, out_val);
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString C4PropertyDelegate::GetDisplayString(const C4Value &v, C4Object *obj) const
|
|
|
|
{
|
|
|
|
return QString(v.GetDataString().getData());
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor C4PropertyDelegate::GetDisplayTextColor(const C4Value &val, class C4Object *obj) const
|
|
|
|
{
|
|
|
|
return QColor(); // invalid = default
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor C4PropertyDelegate::GetDisplayBackgroundColor(const C4Value &val, class C4Object *obj) const
|
|
|
|
{
|
|
|
|
return QColor(); // invalid = default
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
C4PropertyPath C4PropertyDelegate::GetPathForProperty(C4ConsoleQtPropListModelProperty *editor_prop) const
|
|
|
|
{
|
|
|
|
C4PropertyPath path;
|
|
|
|
if (editor_prop->property_path.IsEmpty())
|
2016-05-28 13:30:54 +00:00
|
|
|
path = C4PropertyPath(editor_prop->parent_value.getPropList());
|
2016-05-25 03:06:00 +00:00
|
|
|
else
|
|
|
|
path = editor_prop->property_path;
|
|
|
|
C4PropertyPath subpath;
|
|
|
|
if (GetSetFunction())
|
|
|
|
subpath = C4PropertyPath(path, GetSetFunction(), IsGlobalSetFunction() ? C4PropertyPath::PPT_GlobalSetFunction : C4PropertyPath::PPT_SetFunction);
|
|
|
|
else
|
2016-05-28 13:30:54 +00:00
|
|
|
if (editor_prop->key)
|
|
|
|
subpath = C4PropertyPath(path, editor_prop->key->GetCStr());
|
|
|
|
else
|
|
|
|
subpath = path;
|
2016-05-25 03:06:00 +00:00
|
|
|
return subpath;
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateInt::C4PropertyDelegateInt(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
2016-04-09 18:20:31 +00:00
|
|
|
: C4PropertyDelegate(factory, props), min(std::numeric_limits<int32_t>::min()), max(std::numeric_limits<int32_t>::max()), step(1)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-04-09 18:20:31 +00:00
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
min = props->GetPropertyInt(P_Min, min);
|
|
|
|
max = props->GetPropertyInt(P_Max, max);
|
|
|
|
step = props->GetPropertyInt(P_Step, step);
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
void C4PropertyDelegateInt::SetEditorData(QWidget *editor, const C4Value &val, const C4PropertyPath &property_path) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
|
|
|
|
spinBox->setValue(val.getInt());
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateInt::SetModelData(QObject *editor, const C4PropertyPath &property_path) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
|
|
|
|
spinBox->interpretText();
|
|
|
|
property_path.SetProperty(C4VInt(spinBox->value()));
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
QWidget *C4PropertyDelegateInt::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
QSpinBox *editor = new QSpinBox(parent);
|
2016-04-09 18:20:31 +00:00
|
|
|
editor->setMinimum(min);
|
|
|
|
editor->setMaximum(max);
|
|
|
|
editor->setSingleStep(step);
|
2016-04-04 03:46:58 +00:00
|
|
|
connect(editor, &QSpinBox::editingFinished, this, [editor, this]() {
|
|
|
|
emit EditingDoneSignal(editor);
|
|
|
|
});
|
|
|
|
return editor;
|
|
|
|
}
|
|
|
|
|
2016-06-16 04:40:25 +00:00
|
|
|
C4PropertyDelegateString::C4PropertyDelegateString(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegate(factory, props)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateString::SetEditorData(QWidget *editor, const C4Value &val, const C4PropertyPath &property_path) const
|
|
|
|
{
|
|
|
|
Editor *line_edit = static_cast<Editor*>(editor);
|
|
|
|
C4String *s = val.getStr();
|
|
|
|
line_edit->setText(QString(s ? s->GetCStr() : ""));
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateString::SetModelData(QObject *editor, const C4PropertyPath &property_path) const
|
|
|
|
{
|
|
|
|
Editor *line_edit = static_cast<Editor*>(editor);
|
|
|
|
// Only set model data when pressing Enter explicitely; not just when leaving
|
|
|
|
if (line_edit->commit_pending)
|
|
|
|
{
|
|
|
|
QString new_value = line_edit->text();
|
|
|
|
// TODO: Would be better to handle escaping in the C4Value-to-string code
|
|
|
|
new_value = new_value.replace("\\", "\\\\").replace("\"", "\\\"");
|
|
|
|
property_path.SetProperty(C4VString(new_value.toUtf8()));
|
|
|
|
line_edit->commit_pending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget *C4PropertyDelegateString::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
|
|
|
{
|
|
|
|
Editor *editor = new Editor(parent);
|
|
|
|
// EditingDone only on Return; not just when leaving edit field
|
|
|
|
connect(editor, &QLineEdit::returnPressed, editor, [this, editor]() {
|
|
|
|
editor->commit_pending = true;
|
|
|
|
emit EditingDoneSignal(editor);
|
|
|
|
});
|
|
|
|
return editor;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString C4PropertyDelegateString::GetDisplayString(const C4Value &v, C4Object *obj) const
|
|
|
|
{
|
|
|
|
// Raw string without ""
|
|
|
|
C4String *s = v.getStr();
|
|
|
|
return QString(s ? s->GetCStr() : "");
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateLabelAndButtonWidget::C4PropertyDelegateLabelAndButtonWidget(QWidget *parent)
|
2016-06-03 05:20:43 +00:00
|
|
|
: QWidget(parent), layout(nullptr), label(nullptr), button(nullptr), button_pending(false)
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
layout = new QHBoxLayout(this);
|
|
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
layout->setMargin(0);
|
|
|
|
layout->setSpacing(0);
|
|
|
|
label = new QLabel(this);
|
2016-06-08 04:37:48 +00:00
|
|
|
QPalette palette = label->palette();
|
|
|
|
palette.setColor(label->foregroundRole(), palette.color(QPalette::HighlightedText));
|
|
|
|
palette.setColor(label->backgroundRole(), palette.color(QPalette::Highlight));
|
|
|
|
label->setPalette(palette);
|
2016-04-17 02:02:24 +00:00
|
|
|
layout->addWidget(label);
|
|
|
|
button = new QPushButton(QString(LoadResStr("IDS_CNS_MORE")), this);
|
|
|
|
layout->addWidget(button);
|
2016-06-15 04:15:08 +00:00
|
|
|
// Make sure to draw over view in background
|
|
|
|
setPalette(palette);
|
2016-06-08 04:37:48 +00:00
|
|
|
setAutoFillBackground(true);
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
C4PropertyDelegateDescendPath::C4PropertyDelegateDescendPath(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegate(factory, props), edit_on_selection(true)
|
|
|
|
{
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
info_proplist = C4VPropList(props->GetPropertyPropList(P_Elements));
|
|
|
|
edit_on_selection = props->GetPropertyBool(P_EditOnSelection, edit_on_selection);
|
|
|
|
}
|
|
|
|
}
|
2016-06-06 05:54:05 +00:00
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
void C4PropertyDelegateDescendPath::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
|
|
|
|
{
|
|
|
|
Editor *editor = static_cast<Editor *>(aeditor);
|
|
|
|
editor->label->setText(GetDisplayString(val, nullptr));
|
|
|
|
editor->last_value = val;
|
|
|
|
editor->property_path = property_path;
|
|
|
|
if (editor->button_pending) emit editor->button->pressed();
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget *C4PropertyDelegateDescendPath::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
|
|
|
{
|
|
|
|
// Otherwise create display and button to descend path
|
|
|
|
Editor *editor;
|
|
|
|
std::unique_ptr<Editor> peditor((editor = new Editor(parent)));
|
|
|
|
connect(editor->button, &QPushButton::pressed, this, [editor, this]() {
|
|
|
|
// Value to descend into: Use last value on auto-press because it will not have been updated into the game yet
|
|
|
|
// (and cannot be without going async in network mode)
|
|
|
|
// On regular button press, re-resolve path to value
|
|
|
|
C4Value val = editor->button_pending ? editor->last_value : editor->property_path.ResolveValue();
|
|
|
|
if (val.getPropList() || val.getArray())
|
|
|
|
{
|
|
|
|
C4PropList *info_proplist = this->info_proplist.getPropList();
|
|
|
|
if (!info_proplist) info_proplist = val.getPropList();
|
|
|
|
this->factory->GetPropertyModel()->DescendPath(val, info_proplist, editor->property_path);
|
|
|
|
::Console.EditCursor.InvalidateSelection();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (by_selection && edit_on_selection) editor->button_pending = true;
|
|
|
|
return peditor.release();
|
|
|
|
}
|
|
|
|
|
2016-06-06 05:54:05 +00:00
|
|
|
C4PropertyDelegateArray::C4PropertyDelegateArray(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegateDescendPath(factory, props), max_array_display(0), element_delegate(nullptr)
|
2016-06-03 05:20:43 +00:00
|
|
|
{
|
2016-06-06 05:54:05 +00:00
|
|
|
if (props)
|
2016-06-03 05:20:43 +00:00
|
|
|
{
|
2016-06-06 05:54:05 +00:00
|
|
|
max_array_display = props->GetPropertyInt(P_Display);
|
2016-06-03 05:20:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-06 05:54:05 +00:00
|
|
|
QString C4PropertyDelegateArray::GetDisplayString(const C4Value &v, C4Object *obj) const
|
|
|
|
{
|
|
|
|
C4ValueArray *arr = v.getArray();
|
|
|
|
if (!arr) return QString(LoadResStr("IDS_CNS_INVALID"));
|
|
|
|
int32_t n = v._getArray()->GetSize();
|
|
|
|
if (!element_delegate) element_delegate = factory->GetDelegateByValue(info_proplist);
|
|
|
|
if (max_array_display && n)
|
|
|
|
{
|
|
|
|
QString result = "[";
|
|
|
|
for (int32_t i = 0; i < std::min<int32_t>(n, max_array_display); ++i)
|
|
|
|
{
|
|
|
|
if (i) result += ",";
|
|
|
|
result += element_delegate->GetDisplayString(v._getArray()->GetItem(i), obj);
|
|
|
|
}
|
|
|
|
if (n > max_array_display) result += ",...";
|
|
|
|
result += "]";
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Default display (or display with 0 elements): Just show element number
|
|
|
|
return QString(LoadResStr("IDS_CNS_ARRAYSHORT")).arg(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyDelegatePropList::C4PropertyDelegatePropList(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegateDescendPath(factory, props)
|
|
|
|
{
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
display_string = props->GetPropertyStr(P_Display);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString C4PropertyDelegatePropList::GetDisplayString(const C4Value &v, C4Object *obj) const
|
|
|
|
{
|
|
|
|
C4PropList *data = v.getPropList();
|
|
|
|
if (!data) return QString(LoadResStr("IDS_CNS_INVALID"));
|
|
|
|
if (!display_string) return QString("{...}");
|
|
|
|
// Replace all {{name}} by property values of name
|
|
|
|
QString result = display_string->GetCStr();
|
|
|
|
int32_t pos0, pos1;
|
|
|
|
C4Value cv;
|
|
|
|
while ((pos0 = result.indexOf("{{")) >= 0)
|
|
|
|
{
|
|
|
|
pos1 = result.indexOf("}}", pos0+2);
|
|
|
|
if (pos1 < 0) break; // placeholder not closed
|
|
|
|
QString substring = result.mid(pos0+2, pos1-pos0-2);
|
|
|
|
if (!data->GetPropertyByS(::Strings.RegString(substring.toUtf8()), &cv)) cv.Set0();
|
|
|
|
// TODO: May want to resolve child delegates for this in the future
|
|
|
|
// For now, just use GetDataString()
|
|
|
|
result.replace(pos0, pos1 - pos0 + 2, cv.GetDataString().getData());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateColor::C4PropertyDelegateColor(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegate(factory, props)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetTextColorForBackground(uint32_t background_color)
|
|
|
|
{
|
|
|
|
// White text on dark background; black text on bright background
|
|
|
|
uint8_t r = (background_color >> 16) & 0xff;
|
|
|
|
uint8_t g = (background_color >> 8) & 0xff;
|
|
|
|
uint8_t b = (background_color >> 0) & 0xff;
|
|
|
|
int32_t lgt = r * 30 + g * 59 + b * 11;
|
|
|
|
return (lgt > 16000) ? 0 : 0xffffff;
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
void C4PropertyDelegateColor::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
Editor *editor = static_cast<Editor *>(aeditor);
|
|
|
|
uint32_t background_color = static_cast<uint32_t>(val.getInt()) & 0xffffff;
|
|
|
|
uint32_t foreground_color = GetTextColorForBackground(background_color);
|
|
|
|
QPalette palette = editor->label->palette();
|
|
|
|
palette.setColor(editor->label->backgroundRole(), QColor(QRgb(background_color)));
|
|
|
|
palette.setColor(editor->label->foregroundRole(), QColor(QRgb(foreground_color)));
|
|
|
|
editor->label->setPalette(palette);
|
|
|
|
editor->label->setAutoFillBackground(true);
|
|
|
|
editor->label->setText(GetDisplayString(val, NULL));
|
|
|
|
editor->last_value = val;
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateColor::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
Editor *editor = static_cast<Editor *>(aeditor);
|
|
|
|
property_path.SetProperty(editor->last_value);
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
QWidget *C4PropertyDelegateColor::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
Editor *editor;
|
|
|
|
std::unique_ptr<Editor> peditor((editor = new Editor(parent)));
|
|
|
|
connect(editor->button, &QPushButton::pressed, this, [editor, this]() {
|
2016-04-25 20:17:10 +00:00
|
|
|
QColor clr = QColorDialog::getColor(QColor(editor->last_value.getInt()), editor, QString(), QColorDialog::ShowAlphaChannel);
|
2016-04-17 02:02:24 +00:00
|
|
|
editor->last_value.SetInt(clr.rgba());
|
2016-05-25 03:06:00 +00:00
|
|
|
this->SetEditorData(editor, editor->last_value, C4PropertyPath()); // force update on display
|
2016-04-17 02:02:24 +00:00
|
|
|
emit EditingDoneSignal(editor);
|
|
|
|
});
|
|
|
|
return peditor.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString C4PropertyDelegateColor::GetDisplayString(const C4Value &v, C4Object *obj) const
|
|
|
|
{
|
2016-04-25 20:17:10 +00:00
|
|
|
return QString("#%1").arg(uint32_t(v.getInt()), 8, 16, QChar('0'));
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QColor C4PropertyDelegateColor::GetDisplayTextColor(const C4Value &val, class C4Object *obj) const
|
|
|
|
{
|
|
|
|
uint32_t background_color = static_cast<uint32_t>(val.getInt()) & 0xffffff;
|
|
|
|
uint32_t foreground_color = GetTextColorForBackground(background_color);
|
|
|
|
return QColor(foreground_color);
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor C4PropertyDelegateColor::GetDisplayBackgroundColor(const C4Value &val, class C4Object *obj) const
|
|
|
|
{
|
|
|
|
return static_cast<uint32_t>(val.getInt()) & 0xffffff;
|
|
|
|
}
|
|
|
|
|
2016-06-15 04:15:08 +00:00
|
|
|
C4DeepQComboBox::C4DeepQComboBox(QWidget *parent)
|
2016-06-16 04:09:53 +00:00
|
|
|
: QComboBox(parent), descending(false), item_clicked(false), last_popup_height(0)
|
2016-06-15 04:15:08 +00:00
|
|
|
{
|
|
|
|
QTreeView *view = new QTreeView(this);
|
|
|
|
view->setFrameShape(QFrame::NoFrame);
|
|
|
|
view->setSelectionBehavior(QTreeView::SelectRows);
|
|
|
|
view->setAllColumnsShowFocus(true);
|
|
|
|
view->header()->hide();
|
2016-06-16 04:09:53 +00:00
|
|
|
// On expansion, enlarge view if necessery
|
|
|
|
connect(view, &QTreeView::expanded, this, [this, view](const QModelIndex &index)
|
|
|
|
{
|
|
|
|
if (this->model() && view->parentWidget())
|
|
|
|
{
|
|
|
|
int child_row_count = this->model()->rowCount(index);
|
|
|
|
if (child_row_count > 0)
|
|
|
|
{
|
|
|
|
// Get space to contain expanded leaf+1 item
|
|
|
|
QModelIndex last_index = this->model()->index(child_row_count - 1, 0, index);
|
|
|
|
int needed_height = view->visualRect(last_index).bottom() - view->visualRect(index).top() + view->height() - view->parentWidget()->height() + view->visualRect(last_index).height();
|
|
|
|
int available_height = QApplication::desktop()->availableGeometry(view->mapToGlobal(QPoint(1, 1))).height(); // but do not expand past screen size
|
|
|
|
int new_height = std::min(needed_height, available_height - 20);
|
|
|
|
if (view->parentWidget()->height() < new_height) view->parentWidget()->resize(view->parentWidget()->width(), (this->last_popup_height=new_height));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-06-15 04:15:08 +00:00
|
|
|
setView(view);
|
|
|
|
view->viewport()->installEventFilter(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4DeepQComboBox::showPopup()
|
|
|
|
{
|
|
|
|
// New selection: Reset to root of model
|
|
|
|
setRootModelIndex(QModelIndex());
|
|
|
|
QComboBox::showPopup();
|
|
|
|
view()->setMinimumWidth(200); // prevent element list from becoming too small in nested dialogues
|
2016-06-16 04:09:53 +00:00
|
|
|
if (last_popup_height && view()->parentWidget()) view()->parentWidget()->resize(view()->parentWidget()->width(), last_popup_height);
|
2016-06-15 04:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void C4DeepQComboBox::hidePopup()
|
|
|
|
{
|
|
|
|
QModelIndex current = view()->currentIndex();
|
|
|
|
QVariant selected_data = model()->data(current, Qt::UserRole + 1);
|
2016-06-16 04:09:53 +00:00
|
|
|
if (item_clicked && (selected_data.type() != QVariant::Int || !descending))
|
2016-06-15 04:15:08 +00:00
|
|
|
{
|
2016-06-16 04:09:53 +00:00
|
|
|
// Clicked somewhere into the list box: Avoid closing to allow navigation in the tree
|
2016-06-15 04:15:08 +00:00
|
|
|
if (descending)
|
|
|
|
{
|
2016-06-16 04:09:53 +00:00
|
|
|
// Clicked an item text in the tree: Shortcut to opening that tree
|
2016-06-15 04:15:08 +00:00
|
|
|
QTreeView *tview = static_cast<QTreeView *>(view());
|
2016-06-16 04:09:53 +00:00
|
|
|
if (!tview->isExpanded(current))
|
2016-06-15 04:15:08 +00:00
|
|
|
{
|
2016-06-16 04:09:53 +00:00
|
|
|
tview->setExpanded(current, true);
|
2016-06-15 04:15:08 +00:00
|
|
|
int32_t child_row_count = model()->rowCount(current);
|
|
|
|
tview->scrollTo(model()->index(child_row_count - 1, 0, current), QAbstractItemView::EnsureVisible);
|
|
|
|
tview->scrollTo(current, QAbstractItemView::EnsureVisible);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Otherwise, finish selection
|
2016-06-16 04:09:53 +00:00
|
|
|
setRootModelIndex(current.parent());
|
|
|
|
setCurrentIndex(current.row());
|
2016-06-15 04:15:08 +00:00
|
|
|
QComboBox::hidePopup();
|
|
|
|
}
|
|
|
|
descending = item_clicked = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4DeepQComboBox::setCurrentModelIndex(QModelIndex new_index)
|
|
|
|
{
|
|
|
|
setRootModelIndex(new_index.parent());
|
|
|
|
setCurrentIndex(new_index.row());
|
|
|
|
}
|
|
|
|
|
2016-06-16 04:09:53 +00:00
|
|
|
// event filter for view: Catch mouse clicks to prevent closing from simple mouse clicks
|
2016-06-15 04:15:08 +00:00
|
|
|
bool C4DeepQComboBox::eventFilter(QObject *obj, QEvent *event)
|
|
|
|
{
|
|
|
|
if (obj == view()->viewport() && event->type() == QEvent::MouseButtonPress)
|
|
|
|
{
|
|
|
|
QPoint pos = static_cast<QMouseEvent *>(event)->pos();
|
|
|
|
QModelIndex pressed_index = view()->indexAt(pos);
|
2016-06-16 04:09:53 +00:00
|
|
|
item_clicked = true;
|
2016-06-15 04:15:08 +00:00
|
|
|
descending = view()->visualRect(pressed_index).contains(pos);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, C4PropList *props, const C4ValueArray *poptions)
|
2016-04-09 18:20:31 +00:00
|
|
|
: C4PropertyDelegate(factory, props)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
// Build enum options from C4Value definitions in script
|
2016-04-09 18:20:31 +00:00
|
|
|
if (!poptions && props) poptions = props->GetPropertyArray(P_Options);
|
2016-06-08 04:37:48 +00:00
|
|
|
C4String *default_option_key, *default_value_key = nullptr;
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
default_option_key = props->GetPropertyStr(P_OptionKey);
|
|
|
|
default_value_key = props->GetPropertyStr(P_ValueKey);
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
if (poptions)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-04-09 18:20:31 +00:00
|
|
|
options.reserve(poptions->GetSize());
|
|
|
|
for (int32_t i = 0; i < poptions->GetSize(); ++i)
|
|
|
|
{
|
|
|
|
const C4Value &v = poptions->GetItem(i);
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropList *props = v.getPropList();
|
2016-04-09 18:20:31 +00:00
|
|
|
if (!props) continue;
|
|
|
|
Option option;
|
|
|
|
option.name = props->GetPropertyStr(P_Name);
|
|
|
|
if (!option.name.Get()) option.name = ::Strings.RegString("???");
|
2016-06-15 04:15:08 +00:00
|
|
|
option.group = props->GetPropertyStr(P_Group);
|
2016-04-09 18:20:31 +00:00
|
|
|
option.value_key = props->GetPropertyStr(P_ValueKey);
|
2016-06-08 04:37:48 +00:00
|
|
|
if (!option.value_key) option.value_key = default_value_key;
|
2016-04-09 18:20:31 +00:00
|
|
|
props->GetProperty(P_Value, &option.value);
|
|
|
|
option.type = C4V_Type(props->GetPropertyInt(P_Type, C4V_Any));
|
|
|
|
option.option_key = props->GetPropertyStr(P_OptionKey);
|
2016-06-08 04:37:48 +00:00
|
|
|
if (!option.option_key) option.option_key = default_option_key;
|
2016-04-09 18:20:31 +00:00
|
|
|
// Derive storage type from given elements in delegate definition
|
|
|
|
if (option.type != C4V_Any)
|
|
|
|
option.storage_type = Option::StorageByType;
|
2016-06-08 04:37:48 +00:00
|
|
|
else if (option.option_key && option.value.GetType() != C4V_Nil)
|
2016-04-09 18:20:31 +00:00
|
|
|
option.storage_type = Option::StorageByKey;
|
|
|
|
else
|
|
|
|
option.storage_type = Option::StorageByValue;
|
|
|
|
// Child delegate for value (resolved at runtime because there may be circular references)
|
|
|
|
props->GetProperty(P_Delegate, &option.adelegate_val);
|
|
|
|
options.push_back(option);
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-15 04:15:08 +00:00
|
|
|
QStandardItemModel *C4PropertyDelegateEnum::CreateOptionModel() const
|
|
|
|
{
|
|
|
|
// Create a QStandardItemModel tree from all options and their groups
|
|
|
|
std::unique_ptr<QStandardItemModel> model(new QStandardItemModel());
|
|
|
|
model->setColumnCount(1);
|
|
|
|
int idx = 0;
|
|
|
|
for (const Option &opt : options)
|
|
|
|
{
|
|
|
|
QStandardItem *parent = model->invisibleRootItem();
|
|
|
|
if (opt.group)
|
|
|
|
{
|
|
|
|
QStringList group_names = QString(opt.group->GetCStr()).split(QString("/"));
|
|
|
|
for (const QString &group_name : group_names)
|
|
|
|
{
|
|
|
|
int row_index = -1;
|
|
|
|
for (int check_row_index = 0; check_row_index < parent->rowCount(); ++check_row_index)
|
|
|
|
if (parent->child(check_row_index, 0)->text() == group_name)
|
|
|
|
{
|
|
|
|
row_index = check_row_index;
|
|
|
|
parent = parent->child(check_row_index, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (row_index < 0)
|
|
|
|
{
|
|
|
|
QStandardItem *new_group = new QStandardItem(group_name);
|
|
|
|
parent->appendRow(new_group);
|
|
|
|
parent = new_group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QStandardItem *new_item = new QStandardItem(QString(opt.name->GetCStr()));
|
|
|
|
new_item->setData(QVariant(idx), Qt::UserRole + 1);
|
|
|
|
parent->appendRow(new_item);
|
|
|
|
++idx;
|
|
|
|
}
|
|
|
|
return model.release();
|
|
|
|
}
|
|
|
|
|
2016-04-09 18:20:31 +00:00
|
|
|
void C4PropertyDelegateEnum::ReserveOptions(int32_t num)
|
|
|
|
{
|
|
|
|
options.reserve(num);
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
void C4PropertyDelegateEnum::AddTypeOption(C4String *name, C4V_Type type, const C4Value &val, C4PropertyDelegate *adelegate)
|
|
|
|
{
|
|
|
|
Option option;
|
|
|
|
option.name = name;
|
|
|
|
option.type = type;
|
|
|
|
option.value = val;
|
|
|
|
option.storage_type = Option::StorageByType;
|
|
|
|
option.adelegate = adelegate;
|
|
|
|
options.push_back(option);
|
|
|
|
}
|
|
|
|
|
2016-06-16 01:57:59 +00:00
|
|
|
void C4PropertyDelegateEnum::AddConstOption(C4String *name, const C4Value &val, C4String *group)
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
|
|
|
Option option;
|
|
|
|
option.name = name;
|
2016-06-16 01:57:59 +00:00
|
|
|
option.group = group;
|
2016-04-09 18:20:31 +00:00
|
|
|
option.value = val;
|
|
|
|
option.storage_type = Option::StorageByValue;
|
|
|
|
options.push_back(option);
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
int32_t C4PropertyDelegateEnum::GetOptionByValue(const C4Value &val) const
|
|
|
|
{
|
|
|
|
int32_t iopt = 0;
|
2016-04-17 02:02:24 +00:00
|
|
|
bool match = false;
|
2016-04-04 03:46:58 +00:00
|
|
|
for (auto &option : options)
|
|
|
|
{
|
|
|
|
switch (option.storage_type)
|
|
|
|
{
|
|
|
|
case Option::StorageByType:
|
|
|
|
match = (val.GetTypeEx() == option.type);
|
|
|
|
break;
|
|
|
|
case Option::StorageByValue:
|
|
|
|
match = (val == option.value);
|
|
|
|
break;
|
|
|
|
case Option::StorageByKey: // Compare value to value in property. Assume undefined as nil.
|
|
|
|
{
|
|
|
|
C4PropList *props = val.getPropList();
|
2016-06-03 05:20:43 +00:00
|
|
|
C4PropList *def_props = option.value.getPropList();
|
|
|
|
if (props && def_props)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
C4Value propval, defval;
|
2016-04-04 03:46:58 +00:00
|
|
|
props->GetPropertyByS(option.option_key.Get(), &propval);
|
2016-06-03 05:20:43 +00:00
|
|
|
def_props->GetPropertyByS(option.option_key.Get(), &defval);
|
|
|
|
match = (defval == propval);
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
if (match) break;
|
|
|
|
++iopt;
|
|
|
|
}
|
|
|
|
// If no option matches, just pick first
|
2016-04-17 02:02:24 +00:00
|
|
|
return match ? iopt : -1;
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
void C4PropertyDelegateEnum::UpdateEditorParameter(C4PropertyDelegateEnum::Editor *editor, bool by_selection) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
// Recreate parameter settings editor associated with the currently selected option of an enum
|
|
|
|
if (editor->parameter_widget)
|
|
|
|
{
|
|
|
|
editor->parameter_widget->deleteLater();
|
|
|
|
editor->parameter_widget = NULL;
|
|
|
|
}
|
|
|
|
int32_t idx = editor->option_box->currentIndex();
|
|
|
|
if (idx < 0 || idx >= options.size()) return;
|
|
|
|
const Option &option = options[idx];
|
|
|
|
// Lazy-resolve parameter delegate
|
2016-04-25 20:17:10 +00:00
|
|
|
EnsureOptionDelegateResolved(option);
|
2016-04-04 03:46:58 +00:00
|
|
|
// Create editor if needed
|
|
|
|
if (option.adelegate)
|
|
|
|
{
|
|
|
|
// Determine value to be shown in editor
|
2016-06-03 05:20:43 +00:00
|
|
|
C4Value parameter_val;
|
|
|
|
if (!by_selection)
|
|
|
|
{
|
|
|
|
// Showing current selection: From last_val assigned in SetEditorData
|
|
|
|
parameter_val = editor->last_val;
|
2016-06-08 04:37:48 +00:00
|
|
|
if (option.value_key)
|
2016-06-03 05:20:43 +00:00
|
|
|
{
|
|
|
|
C4PropList *props = editor->last_val.getPropList();
|
|
|
|
if (props) props->GetPropertyByS(option.value_key.Get(), ¶meter_val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
// Selecting a new item: Set the default value
|
|
|
|
parameter_val = option.value;
|
|
|
|
// Although the default value is taken directly from SetEditorData, it needs to be set here to make child access into proplists and arrays possible
|
|
|
|
// (note that actual setting is delayed by control queue)
|
|
|
|
editor->last_get_path.SetProperty(parameter_val);
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
// Show it
|
2016-06-03 05:20:43 +00:00
|
|
|
editor->parameter_widget = option.adelegate->CreateEditor(factory, editor, QStyleOptionViewItem(), by_selection);
|
2016-04-04 03:46:58 +00:00
|
|
|
if (editor->parameter_widget)
|
|
|
|
{
|
|
|
|
editor->layout->addWidget(editor->parameter_widget);
|
2016-06-08 04:37:48 +00:00
|
|
|
C4PropertyPath delegate_value_path = editor->last_get_path;
|
|
|
|
if (option.value_key) delegate_value_path = C4PropertyPath(delegate_value_path, option.value_key->GetCStr());
|
|
|
|
option.adelegate->SetEditorData(editor->parameter_widget, parameter_val, delegate_value_path);
|
2016-04-04 03:46:58 +00:00
|
|
|
// Forward editing signals
|
|
|
|
connect(option.adelegate, &C4PropertyDelegate::EditorValueChangedSignal, editor->parameter_widget, [this, editor](QWidget *changed_editor)
|
|
|
|
{
|
|
|
|
if (changed_editor == editor->parameter_widget)
|
|
|
|
if (!editor->updating)
|
|
|
|
emit EditorValueChangedSignal(editor);
|
|
|
|
});
|
|
|
|
connect(option.adelegate, &C4PropertyDelegate::EditingDoneSignal, editor->parameter_widget, [this, editor](QWidget *changed_editor)
|
|
|
|
{
|
|
|
|
if (changed_editor == editor->parameter_widget) emit EditingDoneSignal(editor);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-15 04:15:08 +00:00
|
|
|
QModelIndex C4PropertyDelegateEnum::GetModelIndexByID(QStandardItemModel *model, QStandardItem *parent_item, int32_t id, const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
// Resolve data stored in model to model index in tree
|
|
|
|
for (int row = 0; row < parent_item->rowCount(); ++row)
|
|
|
|
{
|
|
|
|
QStandardItem *child = parent_item->child(row, 0);
|
|
|
|
QVariant v = child->data(Qt::UserRole + 1);
|
|
|
|
if (v.type() == QVariant::Int && v.toInt() == id) return model->index(row, 0, parent);
|
|
|
|
if (child->rowCount())
|
|
|
|
{
|
|
|
|
QModelIndex child_match = GetModelIndexByID(model, child, id, model->index(row, 0, parent));
|
|
|
|
if (child_match.isValid()) return child_match;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
void C4PropertyDelegateEnum::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
Editor *editor = static_cast<Editor*>(aeditor);
|
|
|
|
editor->last_val = val;
|
2016-05-25 03:06:00 +00:00
|
|
|
editor->last_get_path = property_path;
|
2016-04-04 03:46:58 +00:00
|
|
|
editor->updating = true;
|
|
|
|
// Update option selection
|
2016-04-17 02:02:24 +00:00
|
|
|
int32_t index = std::max<int32_t>(GetOptionByValue(val), 0);
|
2016-06-15 04:15:08 +00:00
|
|
|
QStandardItemModel *model = static_cast<QStandardItemModel *>(editor->option_box->model());
|
|
|
|
editor->option_box->setCurrentModelIndex(GetModelIndexByID(model, model->invisibleRootItem(), index, QModelIndex()));
|
2016-04-04 03:46:58 +00:00
|
|
|
// Update parameter
|
2016-06-03 05:20:43 +00:00
|
|
|
UpdateEditorParameter(editor, false);
|
2016-04-04 03:46:58 +00:00
|
|
|
editor->updating = false;
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateEnum::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
// Fetch value from editor
|
|
|
|
Editor *editor = static_cast<Editor*>(aeditor);
|
2016-06-15 04:15:08 +00:00
|
|
|
QStandardItemModel *model = static_cast<QStandardItemModel *>(editor->option_box->model());
|
|
|
|
QModelIndex selected_model_index = model->index(editor->option_box->currentIndex(), 0, editor->option_box->rootModelIndex());
|
|
|
|
QVariant vidx = model->data(selected_model_index, Qt::UserRole + 1);
|
|
|
|
if (vidx.type() != QVariant::Int) return;
|
|
|
|
int32_t idx = vidx.toInt();
|
2016-04-04 03:46:58 +00:00
|
|
|
if (idx < 0 || idx >= options.size()) return;
|
|
|
|
const Option &option = options[idx];
|
|
|
|
// Store directly in value or in a proplist field?
|
|
|
|
C4PropertyPath use_path;
|
|
|
|
if (option.value_key.Get())
|
|
|
|
use_path = C4PropertyPath(property_path, option.value_key->GetCStr());
|
|
|
|
else
|
|
|
|
use_path = property_path;
|
|
|
|
// Value from a parameter or directly from the enum?
|
|
|
|
if (option.adelegate)
|
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
// Default value on enum change
|
|
|
|
if (editor->option_changed) use_path.SetProperty(option.value);
|
2016-04-09 18:20:31 +00:00
|
|
|
// Value from a parameter.
|
|
|
|
// Using a setter function?
|
|
|
|
if (option.adelegate->GetSetFunction())
|
|
|
|
use_path = C4PropertyPath(use_path, option.adelegate->GetSetFunction(), C4PropertyPath::PPT_SetFunction);
|
2016-04-04 03:46:58 +00:00
|
|
|
option.adelegate->SetModelData(editor->parameter_widget, use_path);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// No parameter. Use value.
|
|
|
|
use_path.SetProperty(option.value);
|
|
|
|
}
|
2016-06-03 05:20:43 +00:00
|
|
|
editor->option_changed = false;
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
QWidget *C4PropertyDelegateEnum::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-04-04 23:34:02 +00:00
|
|
|
Editor *editor = new Editor(parent);
|
2016-04-04 03:46:58 +00:00
|
|
|
editor->layout = new QHBoxLayout(editor);
|
|
|
|
editor->layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
editor->layout->setMargin(0);
|
|
|
|
editor->layout->setSpacing(0);
|
|
|
|
editor->updating = true;
|
2016-06-15 04:15:08 +00:00
|
|
|
editor->option_box = new C4DeepQComboBox(editor);
|
2016-04-04 03:46:58 +00:00
|
|
|
editor->layout->addWidget(editor->option_box);
|
2016-04-17 02:02:24 +00:00
|
|
|
for (auto &option : options) editor->option_box->addItem(option.name->GetCStr());
|
2016-04-04 03:46:58 +00:00
|
|
|
void (QComboBox::*currentIndexChanged)(int) = &QComboBox::currentIndexChanged;
|
|
|
|
connect(editor->option_box, currentIndexChanged, editor, [editor, this](int newval) {
|
|
|
|
if (!editor->updating) this->UpdateOptionIndex(editor, newval); });
|
|
|
|
editor->updating = false;
|
2016-06-15 04:15:08 +00:00
|
|
|
editor->option_box->setModel(CreateOptionModel());
|
2016-04-04 03:46:58 +00:00
|
|
|
return editor;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *editor, int newval) const
|
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
editor->option_changed = true;
|
|
|
|
UpdateEditorParameter(editor, true);
|
2016-04-04 03:46:58 +00:00
|
|
|
emit EditorValueChangedSignal(editor);
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateEnum::EnsureOptionDelegateResolved(const Option &option) const
|
|
|
|
{
|
|
|
|
// Lazy-resolve parameter delegate
|
|
|
|
if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil)
|
|
|
|
option.adelegate = factory->GetDelegateByValue(option.adelegate_val);
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
QString C4PropertyDelegateEnum::GetDisplayString(const C4Value &v, class C4Object *obj) const
|
|
|
|
{
|
|
|
|
// Display string from value
|
|
|
|
int32_t idx = GetOptionByValue(v);
|
|
|
|
if (idx < 0)
|
|
|
|
{
|
|
|
|
// Value not found: Default display
|
|
|
|
return C4PropertyDelegate::GetDisplayString(v, obj);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Value found: Display option string plus parameter
|
2016-04-25 20:17:10 +00:00
|
|
|
const Option &option = options[idx];
|
2016-04-17 02:02:24 +00:00
|
|
|
QString result = option.name->GetCStr();
|
|
|
|
// Lazy-resolve parameter delegate
|
2016-04-25 20:17:10 +00:00
|
|
|
EnsureOptionDelegateResolved(option);
|
2016-04-17 02:02:24 +00:00
|
|
|
if (option.adelegate)
|
|
|
|
{
|
|
|
|
C4Value param_val = v;
|
|
|
|
if (option.value_key.Get())
|
|
|
|
{
|
|
|
|
C4PropList *vp = v.getPropList();
|
|
|
|
if (vp) vp->GetPropertyByS(option.value_key, ¶m_val);
|
|
|
|
}
|
|
|
|
result += " ";
|
|
|
|
result += option.adelegate->GetDisplayString(param_val, obj);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
const C4PropertyDelegateShape *C4PropertyDelegateEnum::GetShapeDelegate(const C4Value &val) const
|
|
|
|
{
|
|
|
|
// Does this delegate own a shape? Forward decision into selected option.
|
|
|
|
int32_t option_idx = GetOptionByValue(val);
|
|
|
|
if (option_idx < 0) return nullptr;
|
|
|
|
const Option &option = options[option_idx];
|
|
|
|
EnsureOptionDelegateResolved(option);
|
|
|
|
if (!option.adelegate) return nullptr;
|
|
|
|
C4Value param_val = val;
|
|
|
|
if (option.value_key.Get())
|
|
|
|
{
|
|
|
|
C4PropList *vp = val.getPropList();
|
|
|
|
if (vp) vp->GetPropertyByS(option.value_key, ¶m_val);
|
|
|
|
}
|
|
|
|
return option.adelegate->GetShapeDelegate(param_val);
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateDef::C4PropertyDelegateDef(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
2016-04-09 18:20:31 +00:00
|
|
|
: C4PropertyDelegateEnum(factory, props)
|
|
|
|
{
|
|
|
|
// Collect sorted definitions
|
2016-06-16 01:57:59 +00:00
|
|
|
C4String *filter_property = props ? props->GetPropertyStr(P_Filter) : nullptr;
|
|
|
|
if (filter_property)
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-06-16 01:57:59 +00:00
|
|
|
// With filter just create a flat list
|
|
|
|
std::vector<C4Def *> defs = ::Definitions.GetAllDefs(filter_property);
|
|
|
|
std::sort(defs.begin(), defs.end(), [](C4Def *a, C4Def *b) -> bool {
|
|
|
|
return strcmp(a->GetName(), b->GetName()) < 0;
|
|
|
|
});
|
|
|
|
// Add them
|
|
|
|
ReserveOptions(defs.size() + 1);
|
|
|
|
AddConstOption(::Strings.RegString("nil"), C4VNull); // nil is always an option
|
|
|
|
for (C4Def *def : defs)
|
|
|
|
{
|
|
|
|
C4RefCntPointer<C4String> option_name = ::Strings.RegString(FormatString("%s (%s)", def->id.ToString(), def->GetName()));
|
|
|
|
AddConstOption(option_name, C4Value(def), nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-06-16 04:09:53 +00:00
|
|
|
AddConstOption(::Strings.RegString("nil"), C4VNull); // nil is always an option
|
2016-06-16 01:57:59 +00:00
|
|
|
// Without filter copy tree from definition list model
|
|
|
|
C4ConsoleQtDefinitionListModel *def_list_model = factory->GetDefinitionListModel();
|
|
|
|
// Recursively add all defs from model
|
|
|
|
AddDefinitions(def_list_model, QModelIndex(), nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateDef::AddDefinitions(C4ConsoleQtDefinitionListModel *def_list_model, QModelIndex parent, C4String *group)
|
|
|
|
{
|
|
|
|
int32_t count = def_list_model->rowCount(parent);
|
|
|
|
for (int32_t i = 0; i < count; ++i)
|
|
|
|
{
|
|
|
|
QModelIndex index = def_list_model->index(i, 0, parent);
|
|
|
|
C4Def *def = def_list_model->GetDefByModelIndex(index);
|
|
|
|
C4RefCntPointer<C4String> name = ::Strings.RegString(def_list_model->GetNameByModelIndex(index));
|
|
|
|
if (def) AddConstOption(name.Get(), C4Value(def), group);
|
|
|
|
if (def_list_model->rowCount(index))
|
|
|
|
{
|
|
|
|
AddDefinitions(def_list_model, index, group ? ::Strings.RegString(FormatString("%s/%s", group->GetCStr(), name->GetCStr()).getData()) : name.Get());
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4PropertyDelegateBool::C4PropertyDelegateBool(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegateEnum(factory, props)
|
|
|
|
{
|
|
|
|
// Add boolean options
|
|
|
|
ReserveOptions(2);
|
|
|
|
AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_FALSE")), C4VBool(false));
|
|
|
|
AddConstOption(::Strings.RegString(LoadResStr("IDS_CNS_TRUE")), C4VBool(true));
|
|
|
|
}
|
|
|
|
|
2016-05-28 13:30:54 +00:00
|
|
|
bool C4PropertyDelegateBool::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
// Force value to bool
|
2016-05-28 13:30:54 +00:00
|
|
|
bool success = GetPropertyValueBase(container, key, index, out_val);
|
2016-04-17 02:02:24 +00:00
|
|
|
if (out_val->GetType() != C4V_Bool) *out_val = C4VBool(!!*out_val);
|
2016-05-28 13:30:54 +00:00
|
|
|
return success;
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyDelegateHasEffect::C4PropertyDelegateHasEffect(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegateBool(factory, props)
|
|
|
|
{
|
|
|
|
if (props) effect = props->GetPropertyStr(P_Effect);
|
|
|
|
}
|
|
|
|
|
2016-05-28 13:30:54 +00:00
|
|
|
bool C4PropertyDelegateHasEffect::GetPropertyValue(const C4Value &container, C4String *key, int32_t index, C4Value *out_val) const
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
const C4Object *obj = container.getObj();
|
2016-04-17 02:02:24 +00:00
|
|
|
if (obj && effect)
|
|
|
|
{
|
|
|
|
bool has_effect = false;
|
|
|
|
for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
|
|
|
|
if (!fx->IsDead())
|
|
|
|
if (!strcmp(fx->GetName(), effect->GetCStr()))
|
|
|
|
{
|
|
|
|
has_effect = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
*out_val = C4VBool(has_effect);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
C4PropertyDelegateC4ValueEnum::C4PropertyDelegateC4ValueEnum(const C4PropertyDelegateFactory *factory, C4PropList *props)
|
2016-04-09 18:20:31 +00:00
|
|
|
: C4PropertyDelegateEnum(factory, props)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
|
|
|
// Add default C4Value selections
|
2016-04-09 18:20:31 +00:00
|
|
|
ReserveOptions(10);
|
2016-04-04 03:46:58 +00:00
|
|
|
AddTypeOption(::Strings.RegString("nil"), C4V_Nil, C4VNull);
|
|
|
|
AddTypeOption(::Strings.RegString("bool"), C4V_Bool, C4VNull, factory->GetDelegateByValue(C4VString("bool")));
|
|
|
|
AddTypeOption(::Strings.RegString("int"), C4V_Int, C4VNull, factory->GetDelegateByValue(C4VString("int")));
|
|
|
|
AddTypeOption(::Strings.RegString("string"), C4V_String, C4VNull, factory->GetDelegateByValue(C4VString("string")));
|
|
|
|
AddTypeOption(::Strings.RegString("array"), C4V_Array, C4VNull, factory->GetDelegateByValue(C4VString("array")));
|
|
|
|
AddTypeOption(::Strings.RegString("function"), C4V_Function, C4VNull, factory->GetDelegateByValue(C4VString("function")));
|
|
|
|
AddTypeOption(::Strings.RegString("object"), C4V_Object, C4VNull, factory->GetDelegateByValue(C4VString("object")));
|
|
|
|
AddTypeOption(::Strings.RegString("def"), C4V_Def, C4VNull, factory->GetDelegateByValue(C4VString("def")));
|
|
|
|
AddTypeOption(::Strings.RegString("effect"), C4V_Effect, C4VNull, factory->GetDelegateByValue(C4VString("effect")));
|
|
|
|
AddTypeOption(::Strings.RegString("proplist"), C4V_PropList, C4VNull, factory->GetDelegateByValue(C4VString("proplist")));
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
C4PropertyDelegateC4ValueInputEditor::C4PropertyDelegateC4ValueInputEditor(QWidget *parent)
|
|
|
|
: QWidget(parent), layout(NULL), edit(NULL), extended_button(NULL), commit_pending(false)
|
|
|
|
{
|
|
|
|
layout = new QHBoxLayout(this);
|
|
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
layout->setMargin(0);
|
|
|
|
layout->setSpacing(0);
|
|
|
|
edit = new QLineEdit(this);
|
|
|
|
layout->addWidget(edit);
|
|
|
|
extended_button = new QPushButton("...", this);
|
|
|
|
extended_button->setMaximumWidth(extended_button->fontMetrics().boundingRect("...").width() + 6);
|
|
|
|
layout->addWidget(extended_button);
|
|
|
|
extended_button->hide();
|
|
|
|
edit->setFocus();
|
|
|
|
setLayout(layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateC4ValueInput::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
|
2016-04-04 23:34:02 +00:00
|
|
|
{
|
|
|
|
Editor *editor = static_cast<Editor *>(aeditor);
|
|
|
|
editor->edit->setText(val.GetDataString().getData());
|
2016-05-28 13:30:54 +00:00
|
|
|
if (val.GetType() == C4V_PropList || val.GetType() == C4V_Array)
|
2016-05-25 03:06:00 +00:00
|
|
|
{
|
|
|
|
editor->extended_button->show();
|
|
|
|
editor->property_path = property_path;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
editor->extended_button->hide();
|
|
|
|
}
|
2016-04-04 23:34:02 +00:00
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateC4ValueInput::SetModelData(QObject *aeditor, const C4PropertyPath &property_path) const
|
2016-04-04 23:34:02 +00:00
|
|
|
{
|
|
|
|
// Only set model data when pressing Enter explicitely; not just when leaving
|
|
|
|
Editor *editor = static_cast<Editor *>(aeditor);
|
|
|
|
if (editor->commit_pending)
|
|
|
|
{
|
|
|
|
property_path.SetProperty(editor->edit->text().toUtf8());
|
|
|
|
editor->commit_pending = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
QWidget *C4PropertyDelegateC4ValueInput::CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const
|
2016-04-04 23:34:02 +00:00
|
|
|
{
|
|
|
|
// Editor is just an edit box plus a "..." button for array/proplist types
|
|
|
|
Editor *editor = new Editor(parent);
|
|
|
|
// EditingDone only on Return; not just when leaving edit field
|
|
|
|
connect(editor->edit, &QLineEdit::returnPressed, editor, [this, editor]() {
|
|
|
|
editor->commit_pending = true;
|
|
|
|
emit EditingDoneSignal(editor);
|
|
|
|
});
|
2016-05-25 03:06:00 +00:00
|
|
|
connect(editor->extended_button, &QPushButton::pressed, editor, [this, editor]() {
|
|
|
|
C4Value val = editor->property_path.ResolveValue();
|
2016-05-28 13:30:54 +00:00
|
|
|
if (val.getPropList() || val.getArray())
|
2016-05-25 03:06:00 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
this->factory->GetPropertyModel()->DescendPath(val, val.getPropList(), editor->property_path);
|
2016-05-25 03:06:00 +00:00
|
|
|
::Console.EditCursor.InvalidateSelection();
|
|
|
|
}
|
|
|
|
});
|
2016-04-04 23:34:02 +00:00
|
|
|
return editor;
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
/* Areas shown in viewport */
|
|
|
|
|
|
|
|
C4PropertyDelegateShape::C4PropertyDelegateShape(const class C4PropertyDelegateFactory *factory, C4PropList *props)
|
|
|
|
: C4PropertyDelegate(factory, props), clr(0xffff0000), can_move_center(false)
|
|
|
|
{
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
shape_type = props->GetPropertyStr(P_Type);
|
|
|
|
clr = props->GetPropertyInt(P_Color) | 0xff000000;
|
|
|
|
can_move_center = props->GetPropertyBool(P_CanMoveCenter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateShape::SetModelData(QObject *editor, const C4PropertyPath &property_path) const
|
|
|
|
{
|
|
|
|
C4ConsoleQtShape *shape = static_cast<C4ConsoleQtShape *>(editor);
|
|
|
|
property_path.SetProperty(shape->GetValue());
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateShape::Paint(QPainter *painter, const QStyleOptionViewItem &option, const C4Value &val) const
|
|
|
|
{
|
|
|
|
// Background color
|
|
|
|
if (option.state & QStyle::State_Selected)
|
|
|
|
painter->fillRect(option.rect, option.palette.highlight());
|
|
|
|
else
|
|
|
|
painter->fillRect(option.rect, option.palette.base());
|
|
|
|
// Draw a frame in shape color
|
|
|
|
painter->save();
|
|
|
|
QColor frame_color = QColor(QRgb(clr & 0xffffff));
|
|
|
|
int32_t width = Clamp<int32_t>(option.rect.height() / 8, 2, 6) &~1;
|
|
|
|
QPen rect_pen(QBrush(frame_color), width, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
|
|
|
|
painter->setPen(rect_pen);
|
|
|
|
QRect inner_rect = option.rect.adjusted(width / 2, width / 2, -width / 2, -width / 2);
|
|
|
|
if (shape_type && shape_type->GetData() == "circle")
|
|
|
|
{
|
|
|
|
painter->drawEllipse(inner_rect);
|
|
|
|
if (can_move_center) painter->drawPoint(inner_rect.center());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
painter->drawRect(inner_rect);
|
|
|
|
}
|
|
|
|
painter->restore();
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* Delegate factory: Create delegates based on the C4Value type */
|
|
|
|
|
2016-06-08 04:37:48 +00:00
|
|
|
C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByPropList(C4PropList *props) const
|
|
|
|
{
|
|
|
|
if (props)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-06-08 04:37:48 +00:00
|
|
|
const C4String *str = props->GetPropertyStr(P_Type);
|
|
|
|
if (str)
|
|
|
|
{
|
|
|
|
// create default base types
|
|
|
|
if (str->GetData() == "int") return new C4PropertyDelegateInt(this, props);
|
2016-06-16 04:40:25 +00:00
|
|
|
if (str->GetData() == "string") return new C4PropertyDelegateString(this, props);
|
2016-06-08 04:37:48 +00:00
|
|
|
if (str->GetData() == "array") return new C4PropertyDelegateArray(this, props);
|
|
|
|
if (str->GetData() == "proplist") return new C4PropertyDelegatePropList(this, props);
|
|
|
|
if (str->GetData() == "color") return new C4PropertyDelegateColor(this, props);
|
|
|
|
if (str->GetData() == "def") return new C4PropertyDelegateDef(this, props);
|
|
|
|
if (str->GetData() == "enum") return new C4PropertyDelegateEnum(this, props);
|
|
|
|
if (str->GetData() == "bool") return new C4PropertyDelegateBool(this, props);
|
|
|
|
if (str->GetData() == "has_effect") return new C4PropertyDelegateHasEffect(this, props);
|
|
|
|
if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this, props);
|
|
|
|
if (str->GetData() == "rect" || str->GetData() == "circle") return new C4PropertyDelegateShape(this, props);
|
|
|
|
if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this, props);
|
|
|
|
// unknown type
|
|
|
|
LogF("Invalid delegate type: %s.", str->GetCStr());
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
2016-06-08 04:37:48 +00:00
|
|
|
// Default fallback
|
|
|
|
return new C4PropertyDelegateC4ValueInput(this, props);
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByValue(const C4Value &val) const
|
|
|
|
{
|
2016-06-08 04:37:48 +00:00
|
|
|
auto iter = delegates.find(val.getPropList());
|
2016-04-04 03:46:58 +00:00
|
|
|
if (iter != delegates.end()) return iter->second.get();
|
2016-06-08 04:37:48 +00:00
|
|
|
C4PropertyDelegate *new_delegate = CreateDelegateByPropList(val.getPropList());
|
|
|
|
delegates.insert(std::make_pair(val.getPropList(), std::unique_ptr<C4PropertyDelegate>(new_delegate)));
|
2016-04-04 03:46:58 +00:00
|
|
|
return new_delegate;
|
|
|
|
}
|
|
|
|
|
|
|
|
C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByIndex(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
|
|
|
if (!prop) return NULL;
|
|
|
|
if (!prop->delegate) prop->delegate = GetDelegateByValue(prop->delegate_info);
|
|
|
|
return prop->delegate;
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::ClearDelegates()
|
|
|
|
{
|
|
|
|
delegates.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::EditorValueChanged(QWidget *editor)
|
|
|
|
{
|
|
|
|
emit commitData(editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::EditingDone(QWidget *editor)
|
|
|
|
{
|
|
|
|
emit commitData(editor);
|
|
|
|
//emit closeEditor(editor); - done by qt somewhere else...
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::setEditorData(QWidget *editor, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
// Put property value from proplist into editor
|
|
|
|
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
2016-06-06 05:54:05 +00:00
|
|
|
if (!CheckCurrentEditor(d, editor)) return;
|
2016-06-03 05:20:43 +00:00
|
|
|
// Fetch property only first time - ignore further updates to the same value to simplify editing
|
2016-04-04 03:46:58 +00:00
|
|
|
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
2016-06-03 05:20:43 +00:00
|
|
|
if (!prop) return;
|
2016-04-04 03:46:58 +00:00
|
|
|
C4Value val;
|
2016-05-28 13:30:54 +00:00
|
|
|
d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
|
2016-06-03 05:20:43 +00:00
|
|
|
if (!prop->about_to_edit && val == last_edited_value) return;
|
|
|
|
last_edited_value = val;
|
|
|
|
prop->about_to_edit = false;
|
2016-05-28 13:30:54 +00:00
|
|
|
d->SetEditorData(editor, val, d->GetPathForProperty(prop));
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
// Fetch property value from editor and set it into proplist
|
|
|
|
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
2016-06-06 05:54:05 +00:00
|
|
|
if (!CheckCurrentEditor(d, editor)) return;
|
2016-04-04 03:46:58 +00:00
|
|
|
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
2016-04-25 20:17:10 +00:00
|
|
|
SetPropertyData(d, editor, prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4PropertyDelegateFactory::SetPropertyData(const C4PropertyDelegate *d, QObject *editor, C4ConsoleQtPropListModel::Property *editor_prop) const
|
|
|
|
{
|
|
|
|
// Set according to delegate
|
2016-05-25 03:06:00 +00:00
|
|
|
d->SetModelData(editor, d->GetPathForProperty(editor_prop));
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
|
|
|
if (!d) return NULL;
|
|
|
|
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
|
|
|
prop->about_to_edit = true;
|
2016-06-03 05:20:43 +00:00
|
|
|
QWidget *editor = d->CreateEditor(this, parent, option, true);
|
2016-04-25 20:17:10 +00:00
|
|
|
// Connect value change signals (if editing is possible for this property)
|
2016-04-04 03:46:58 +00:00
|
|
|
// For some reason, commitData needs a non-const pointer
|
2016-04-25 20:17:10 +00:00
|
|
|
if (editor)
|
|
|
|
{
|
|
|
|
connect(d, &C4PropertyDelegate::EditorValueChangedSignal, editor, [editor, this](QWidget *signal_editor) {
|
|
|
|
if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditorValueChanged(editor);
|
|
|
|
});
|
|
|
|
connect(d, &C4PropertyDelegate::EditingDoneSignal, editor, [editor, this](QWidget *signal_editor) {
|
|
|
|
if (signal_editor == editor) const_cast<C4PropertyDelegateFactory *>(this)->EditingDone(editor);
|
|
|
|
});
|
|
|
|
}
|
2016-05-23 23:22:05 +00:00
|
|
|
current_editor = editor;
|
2016-06-06 05:54:05 +00:00
|
|
|
current_editor_delegate = d;
|
2016-04-04 03:46:58 +00:00
|
|
|
return editor;
|
|
|
|
}
|
|
|
|
|
2016-05-23 23:22:05 +00:00
|
|
|
void C4PropertyDelegateFactory::destroyEditor(QWidget *editor, const QModelIndex &index) const
|
|
|
|
{
|
2016-06-06 05:54:05 +00:00
|
|
|
if (editor == current_editor)
|
|
|
|
{
|
|
|
|
current_editor = nullptr;
|
|
|
|
current_editor_delegate = nullptr;
|
|
|
|
}
|
2016-05-23 23:22:05 +00:00
|
|
|
QStyledItemDelegate::destroyEditor(editor, index);
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
2016-06-06 05:54:05 +00:00
|
|
|
if (!CheckCurrentEditor(d, editor)) return;
|
2016-04-04 03:46:58 +00:00
|
|
|
return d->UpdateEditorGeometry(editor, option);
|
|
|
|
}
|
|
|
|
|
2016-04-09 18:20:31 +00:00
|
|
|
QSize C4PropertyDelegateFactory::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
int height = QApplication::fontMetrics().height() + 4;
|
|
|
|
return QSize(100, height);
|
|
|
|
}
|
|
|
|
|
2016-04-25 20:17:10 +00:00
|
|
|
void C4PropertyDelegateFactory::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
// Delegate has custom painting?
|
|
|
|
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
|
|
|
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
|
|
|
if (d && prop && d->HasCustomPaint())
|
|
|
|
{
|
|
|
|
C4Value val;
|
2016-05-28 13:30:54 +00:00
|
|
|
d->GetPropertyValue(prop->parent_value, prop->key, index.row(), &val);
|
|
|
|
d->Paint(painter, option, val);
|
|
|
|
return;
|
2016-04-25 20:17:10 +00:00
|
|
|
}
|
|
|
|
// Otherwise use default paint implementation
|
|
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
|
|
}
|
|
|
|
|
2016-05-23 23:22:05 +00:00
|
|
|
void C4PropertyDelegateFactory::OnPropListChanged()
|
|
|
|
{
|
|
|
|
if (current_editor) emit closeEditor(current_editor);
|
|
|
|
}
|
|
|
|
|
2016-06-06 05:54:05 +00:00
|
|
|
bool C4PropertyDelegateFactory::CheckCurrentEditor(C4PropertyDelegate *d, QWidget *editor) const
|
|
|
|
{
|
|
|
|
if (!d || (editor && editor != current_editor) || d != current_editor_delegate)
|
|
|
|
{
|
|
|
|
//const_cast<C4PropertyDelegateFactory *>(this)->emit closeEditor(current_editor);
|
|
|
|
destroyEditor(current_editor, QModelIndex());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-04 03:46:58 +00:00
|
|
|
|
|
|
|
/* Proplist table view */
|
2016-02-12 04:37:13 +00:00
|
|
|
|
2016-04-17 02:02:24 +00:00
|
|
|
C4ConsoleQtPropListModel::C4ConsoleQtPropListModel(C4PropertyDelegateFactory *delegate_factory)
|
2016-06-03 05:20:43 +00:00
|
|
|
: delegate_factory(delegate_factory), selection_model(nullptr)
|
2016-02-12 04:37:13 +00:00
|
|
|
{
|
2016-04-09 18:20:31 +00:00
|
|
|
header_font.setBold(true);
|
2016-06-03 05:20:43 +00:00
|
|
|
connect(this, &C4ConsoleQtPropListModel::ProplistChanged, this, &C4ConsoleQtPropListModel::UpdateSelection, Qt::QueuedConnection);
|
2016-06-08 04:37:48 +00:00
|
|
|
layout_valid = false;
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
bool C4ConsoleQtPropListModel::AddPropertyGroup(C4PropList *add_proplist, int32_t group_index, QString name, C4PropList *target_proplist, C4Object *base_effect_object, C4String *default_selection, int32_t *default_selection_index)
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
|
|
|
const char *editor_prop_prefix = "EditorProp_";
|
|
|
|
auto new_properties = add_proplist->GetSortedLocalProperties(editor_prop_prefix, target_proplist);
|
|
|
|
if (!new_properties.size()) return false;
|
2016-06-08 04:37:48 +00:00
|
|
|
if (property_groups.size() == group_index)
|
|
|
|
{
|
|
|
|
layout_valid = false;
|
|
|
|
property_groups.resize(group_index + 1);
|
|
|
|
}
|
2016-04-17 02:02:24 +00:00
|
|
|
PropertyGroup &properties = property_groups[group_index];
|
|
|
|
C4PropListStatic *proplist_static = add_proplist->IsStatic();
|
|
|
|
properties.name = name;
|
2016-06-08 04:37:48 +00:00
|
|
|
if (properties.props.size() != new_properties.size())
|
|
|
|
{
|
|
|
|
layout_valid = false;
|
|
|
|
properties.props.resize(new_properties.size());
|
|
|
|
}
|
2016-04-17 02:02:24 +00:00
|
|
|
for (int32_t i = 0; i < new_properties.size(); ++i)
|
|
|
|
{
|
2016-04-25 20:17:10 +00:00
|
|
|
Property *prop = &properties.props[i];
|
2016-05-23 23:22:05 +00:00
|
|
|
// Property access path
|
2016-05-28 13:30:54 +00:00
|
|
|
prop->parent_value.SetPropList(target_proplist);
|
2016-05-23 23:22:05 +00:00
|
|
|
if (base_effect_object)
|
|
|
|
// Access to effect
|
|
|
|
prop->property_path = C4PropertyPath(target_proplist->GetEffect(), base_effect_object);
|
|
|
|
else
|
2016-05-25 03:06:00 +00:00
|
|
|
prop->property_path = target_path;
|
2016-06-03 05:20:43 +00:00
|
|
|
// ID for default selection memory
|
|
|
|
const char *prop_id = new_properties[i]->GetCStr() + strlen(editor_prop_prefix);
|
|
|
|
if (default_selection && default_selection->GetData() == prop_id) *default_selection_index = i;
|
2016-05-23 23:22:05 +00:00
|
|
|
// Property data
|
2016-04-25 20:17:10 +00:00
|
|
|
prop->key = NULL;
|
|
|
|
prop->display_name = NULL;
|
|
|
|
prop->delegate_info.Set0(); // default C4Value delegate
|
|
|
|
prop->group_idx = group_index;
|
2016-04-17 02:02:24 +00:00
|
|
|
C4Value published_prop_val;
|
|
|
|
add_proplist->GetPropertyByS(new_properties[i], &published_prop_val);
|
|
|
|
C4PropList *published_prop = published_prop_val.getPropList();
|
|
|
|
if (published_prop)
|
|
|
|
{
|
2016-04-25 20:17:10 +00:00
|
|
|
prop->key = published_prop->GetPropertyStr(P_Key);
|
|
|
|
prop->display_name = published_prop->GetPropertyStr(P_Name);
|
|
|
|
prop->delegate_info.SetPropList(published_prop);
|
|
|
|
}
|
2016-06-03 05:20:43 +00:00
|
|
|
if (!prop->key) properties.props[i].key = ::Strings.RegString(prop_id);
|
2016-04-25 20:17:10 +00:00
|
|
|
if (!prop->display_name) properties.props[i].display_name = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix));
|
|
|
|
prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
|
|
|
|
C4Value v;
|
2016-05-28 13:30:54 +00:00
|
|
|
C4Value v_target_proplist = C4VPropList(target_proplist);
|
|
|
|
prop->delegate->GetPropertyValue(v_target_proplist, prop->key, 0, &v);
|
2016-04-25 20:17:10 +00:00
|
|
|
// Connect editable shape to property
|
|
|
|
const C4PropertyDelegateShape *new_shape_delegate = prop->delegate->GetShapeDelegate(v);
|
|
|
|
if (new_shape_delegate != prop->shape_delegate)
|
|
|
|
{
|
|
|
|
prop->shape_delegate = new_shape_delegate;
|
|
|
|
if (new_shape_delegate)
|
|
|
|
{
|
|
|
|
C4ConsoleQtShape *shape = ::Console.EditCursor.GetShapes()->CreateShape(target_proplist->GetObject(), published_prop, v);
|
|
|
|
C4PropertyDelegateFactory *factory = this->delegate_factory;
|
|
|
|
connect(shape, &C4ConsoleQtShape::ShapeDragged, new_shape_delegate, [factory, new_shape_delegate, shape, prop]() {
|
|
|
|
factory->SetPropertyData(new_shape_delegate, shape, prop);
|
|
|
|
});
|
|
|
|
prop->shape.Set(shape);
|
|
|
|
}
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-25 03:06:00 +00:00
|
|
|
void C4ConsoleQtPropListModel::SetBasePropList(C4PropList *new_proplist)
|
2016-02-12 04:37:13 +00:00
|
|
|
{
|
2016-05-25 03:06:00 +00:00
|
|
|
// Clear stack and select new proplist
|
2016-02-12 04:37:13 +00:00
|
|
|
// Update properties
|
2016-05-28 13:30:54 +00:00
|
|
|
target_value.SetPropList(new_proplist);
|
2016-05-25 03:06:00 +00:00
|
|
|
base_proplist.SetPropList(new_proplist);
|
|
|
|
// objects derive their custom properties
|
2016-05-28 13:30:54 +00:00
|
|
|
info_proplist.SetPropList(target_value.getObj());
|
2016-05-25 03:06:00 +00:00
|
|
|
target_path = C4PropertyPath(new_proplist);
|
|
|
|
target_path_stack.clear();
|
2016-06-03 05:20:43 +00:00
|
|
|
UpdateValue(true);
|
2016-05-28 13:30:54 +00:00
|
|
|
delegate_factory->OnPropListChanged();
|
2016-05-25 03:06:00 +00:00
|
|
|
}
|
|
|
|
|
2016-05-28 13:30:54 +00:00
|
|
|
void C4ConsoleQtPropListModel::DescendPath(const C4Value &new_value, C4PropList *new_info_proplist, const C4PropertyPath &new_path)
|
2016-05-25 03:06:00 +00:00
|
|
|
{
|
|
|
|
// Add previous proplist to stack
|
2016-05-28 13:30:54 +00:00
|
|
|
target_path_stack.push_back(TargetStackEntry(target_path, target_value, info_proplist));
|
2016-05-25 03:06:00 +00:00
|
|
|
// descend
|
2016-05-28 13:30:54 +00:00
|
|
|
target_value = new_value;
|
2016-05-25 03:06:00 +00:00
|
|
|
info_proplist.SetPropList(new_info_proplist);
|
|
|
|
target_path = new_path;
|
2016-06-03 05:20:43 +00:00
|
|
|
UpdateValue(true);
|
2016-05-28 13:30:54 +00:00
|
|
|
delegate_factory->OnPropListChanged();
|
2016-05-25 03:06:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void C4ConsoleQtPropListModel::AscendPath()
|
|
|
|
{
|
|
|
|
// Go up in target stack (if possible)
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (!target_path_stack.size())
|
|
|
|
{
|
|
|
|
SetBasePropList(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TargetStackEntry entry = target_path_stack.back();
|
|
|
|
target_path_stack.pop_back();
|
|
|
|
if (!entry.value || !entry.info_proplist) continue; // property was removed; go up further in stack
|
|
|
|
// Safety: Make sure we're still on the same value
|
|
|
|
C4Value target = entry.path.ResolveValue();
|
|
|
|
if (!target.IsIdenticalTo(entry.value)) continue;
|
|
|
|
// Set new value
|
|
|
|
target_path = entry.path;
|
2016-05-28 13:30:54 +00:00
|
|
|
target_value = entry.value;
|
2016-05-25 03:06:00 +00:00
|
|
|
info_proplist = entry.info_proplist;
|
2016-06-03 05:20:43 +00:00
|
|
|
UpdateValue(true);
|
2016-05-28 13:30:54 +00:00
|
|
|
delegate_factory->OnPropListChanged();
|
2016-05-25 03:06:00 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
void C4ConsoleQtPropListModel::UpdateValue(bool select_default)
|
2016-05-25 03:06:00 +00:00
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
// Update target value from path
|
|
|
|
target_value = target_path.ResolveValue();
|
2016-05-28 13:30:54 +00:00
|
|
|
// Safe-get from C4Values in case any prop lists or arrays got deleted
|
2016-06-03 05:20:43 +00:00
|
|
|
int32_t num_groups, default_selection_group = -1, default_selection_index = -1;
|
2016-05-28 13:30:54 +00:00
|
|
|
switch (target_value.GetType())
|
|
|
|
{
|
|
|
|
case C4V_PropList:
|
2016-06-03 05:20:43 +00:00
|
|
|
num_groups = UpdateValuePropList(target_value._getPropList(), &default_selection_group, &default_selection_index);
|
2016-05-28 13:30:54 +00:00
|
|
|
break;
|
|
|
|
case C4V_Array:
|
2016-06-03 05:20:43 +00:00
|
|
|
num_groups = UpdateValueArray(target_value._getArray(), &default_selection_group, &default_selection_index);
|
2016-05-28 13:30:54 +00:00
|
|
|
break;
|
|
|
|
default: // probably nil
|
|
|
|
num_groups = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Update model range
|
2016-06-08 04:37:48 +00:00
|
|
|
if (num_groups != property_groups.size())
|
|
|
|
{
|
|
|
|
layout_valid = false;
|
|
|
|
property_groups.resize(num_groups);
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
QModelIndex topLeft = index(0, 0, QModelIndex());
|
|
|
|
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1, QModelIndex());
|
|
|
|
emit dataChanged(topLeft, bottomRight);
|
2016-06-08 04:37:48 +00:00
|
|
|
if (!layout_valid) emit layoutChanged();
|
|
|
|
layout_valid = true;
|
2016-06-03 05:20:43 +00:00
|
|
|
// Initial selection
|
|
|
|
if (select_default) emit ProplistChanged(default_selection_group, default_selection_index);
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
void C4ConsoleQtPropListModel::UpdateSelection(int32_t major_sel, int32_t minor_sel) const
|
|
|
|
{
|
|
|
|
if (selection_model)
|
|
|
|
{
|
|
|
|
// Select by indexed elements only
|
|
|
|
selection_model->clearSelection();
|
|
|
|
if (major_sel >= 0)
|
|
|
|
{
|
|
|
|
QModelIndex sel = index(major_sel, 0, QModelIndex());
|
|
|
|
if (minor_sel >= 0) sel = index(minor_sel, 0, sel);
|
|
|
|
selection_model->select(sel, QItemSelectionModel::SelectCurrent);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selection_model->select(QModelIndex(), QItemSelectionModel::SelectCurrent);
|
|
|
|
}
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
int32_t C4ConsoleQtPropListModel::UpdateValuePropList(C4PropList *target_proplist, int32_t *default_selection_group, int32_t *default_selection_index)
|
2016-05-28 13:30:54 +00:00
|
|
|
{
|
|
|
|
assert(target_proplist);
|
2016-05-25 03:06:00 +00:00
|
|
|
C4PropList *base_proplist = this->base_proplist.getPropList();
|
|
|
|
C4PropList *info_proplist = this->info_proplist.getPropList();
|
2016-04-17 02:02:24 +00:00
|
|
|
int32_t num_groups = 0;
|
2016-05-28 13:30:54 +00:00
|
|
|
// Published properties
|
|
|
|
if (info_proplist)
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
C4String *default_selection = info_proplist->GetPropertyStr(P_DefaultEditorProp);
|
2016-05-28 13:30:54 +00:00
|
|
|
C4Object *obj = info_proplist->GetObject();
|
|
|
|
// Properties from effects (no inheritance supported)
|
|
|
|
if (obj)
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
for (C4Effect *fx = obj->pEffects; fx; fx = fx->pNext)
|
2016-05-25 03:06:00 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
QString name = fx->GetName();
|
2016-06-03 05:20:43 +00:00
|
|
|
if (AddPropertyGroup(fx, num_groups, name, fx, obj, nullptr, nullptr))
|
2016-05-28 13:30:54 +00:00
|
|
|
++num_groups;
|
2016-04-17 02:02:24 +00:00
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
}
|
|
|
|
// Properties from object (but not on definition)
|
|
|
|
if (obj || !info_proplist->GetDef())
|
|
|
|
{
|
|
|
|
for (C4PropList *check_proplist = info_proplist; check_proplist; check_proplist = check_proplist->GetPrototype())
|
2016-04-17 02:02:24 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
QString name;
|
|
|
|
C4PropListStatic *proplist_static = check_proplist->IsStatic();
|
|
|
|
if (proplist_static)
|
|
|
|
name = QString(proplist_static->GetDataString().getData());
|
|
|
|
else
|
|
|
|
name = check_proplist->GetName();
|
2016-06-03 05:20:43 +00:00
|
|
|
if (AddPropertyGroup(check_proplist, num_groups, name, target_proplist, nullptr, default_selection, default_selection_index))
|
2016-05-28 13:30:54 +00:00
|
|
|
++num_groups;
|
2016-06-03 05:20:43 +00:00
|
|
|
// Assign group for default selection
|
|
|
|
if (*default_selection_index >= 0)
|
|
|
|
{
|
|
|
|
*default_selection_group = num_groups - 1;
|
|
|
|
default_selection = nullptr; // don't find any other instances
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
// properties from global list for objects
|
|
|
|
if (obj)
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
C4Def *editor_base = C4Id2Def(C4ID::EditorBase);
|
|
|
|
if (editor_base)
|
2016-06-03 05:20:43 +00:00
|
|
|
if (AddPropertyGroup(editor_base, num_groups, LoadResStr("IDS_CNS_OBJECT"), target_proplist, nullptr, nullptr, nullptr))
|
2016-05-28 13:30:54 +00:00
|
|
|
++num_groups;
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
// Always: Internal properties
|
|
|
|
auto new_properties = target_proplist->GetSortedLocalProperties();
|
|
|
|
if (property_groups.size() == num_groups) property_groups.resize(num_groups + 1);
|
|
|
|
PropertyGroup &internal_properties = property_groups[num_groups];
|
|
|
|
internal_properties.name = LoadResStr("IDS_CNS_INTERNAL");
|
|
|
|
internal_properties.props.resize(new_properties.size());
|
|
|
|
for (int32_t i = 0; i < new_properties.size(); ++i)
|
|
|
|
{
|
|
|
|
internal_properties.props[i].parent_value = this->target_value;
|
|
|
|
internal_properties.props[i].property_path = target_path;
|
|
|
|
internal_properties.props[i].key = new_properties[i];
|
|
|
|
internal_properties.props[i].display_name = new_properties[i];
|
|
|
|
internal_properties.props[i].delegate_info.Set0(); // default C4Value delegate
|
|
|
|
internal_properties.props[i].delegate = NULL; // init when needed
|
|
|
|
internal_properties.props[i].group_idx = num_groups;
|
|
|
|
internal_properties.props[i].shape.Clear();
|
|
|
|
internal_properties.props[i].shape_delegate = nullptr;
|
|
|
|
}
|
|
|
|
++num_groups;
|
|
|
|
return num_groups;
|
|
|
|
}
|
|
|
|
|
2016-06-03 05:20:43 +00:00
|
|
|
int32_t C4ConsoleQtPropListModel::UpdateValueArray(C4ValueArray *target_array, int32_t *default_selection_group, int32_t *default_selection_index)
|
2016-05-28 13:30:54 +00:00
|
|
|
{
|
2016-06-08 04:37:48 +00:00
|
|
|
if (property_groups.empty())
|
|
|
|
{
|
|
|
|
layout_valid = false;
|
|
|
|
property_groups.resize(1);
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
PropertyGroup &properties = property_groups[0];
|
2016-06-08 04:37:48 +00:00
|
|
|
if (properties.props.size() != target_array->GetSize())
|
|
|
|
{
|
|
|
|
layout_valid = false;
|
|
|
|
properties.props.resize(target_array->GetSize());
|
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
C4PropertyDelegate *item_delegate = delegate_factory->GetDelegateByValue(info_proplist);
|
|
|
|
for (int32_t i = 0; i < properties.props.size(); ++i)
|
|
|
|
{
|
|
|
|
Property &prop = properties.props[i];
|
|
|
|
prop.property_path = C4PropertyPath(target_path, i);
|
|
|
|
prop.parent_value = target_value;
|
|
|
|
prop.display_name = ::Strings.RegString(FormatString("%d", (int)i).getData());
|
|
|
|
prop.key = nullptr;
|
|
|
|
prop.delegate_info = info_proplist;
|
|
|
|
prop.delegate = item_delegate;
|
|
|
|
prop.about_to_edit = false;
|
|
|
|
prop.group_idx = 0;
|
|
|
|
prop.shape.Clear(); // array elements cannot have shape
|
|
|
|
prop.shape_delegate = nullptr;
|
|
|
|
}
|
|
|
|
return 1; // one group for the values
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int C4ConsoleQtPropListModel::rowCount(const QModelIndex & parent) const
|
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
QModelIndex grandparent;
|
|
|
|
switch (target_value.GetType())
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
case C4V_PropList:
|
|
|
|
// Top level: Property groups
|
|
|
|
if (!parent.isValid()) return property_groups.size();
|
|
|
|
// Mid level: Descend into property lists
|
|
|
|
grandparent = parent.parent();
|
|
|
|
if (!grandparent.isValid())
|
|
|
|
{
|
|
|
|
if (parent.row() >= 0 && parent.row() < property_groups.size())
|
|
|
|
return property_groups[parent.row()].props.size();
|
|
|
|
}
|
|
|
|
return 0; // no 3rd level depth
|
|
|
|
case C4V_Array:
|
|
|
|
// Top level rows only
|
|
|
|
if (!parent.isValid())
|
|
|
|
return target_value.getArray()->GetSize();
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
// probably deleted (=nil)
|
|
|
|
return 0;
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int C4ConsoleQtPropListModel::columnCount(const QModelIndex & parent) const
|
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
return 2; // Name + Data (or Index + Data)
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QVariant C4ConsoleQtPropListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
|
|
|
// Table headers
|
|
|
|
if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal)
|
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
if (section == 0)
|
|
|
|
if (target_value.GetType() == C4V_Array)
|
|
|
|
return QVariant(LoadResStr("IDS_CNS_INDEXSHORT"));
|
|
|
|
else
|
|
|
|
return QVariant(LoadResStr("IDS_CTL_NAME"));
|
2016-04-09 18:20:31 +00:00
|
|
|
if (section == 1) return QVariant(LoadResStr("IDS_CNS_VALUE"));
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) const
|
|
|
|
{
|
2016-04-09 18:20:31 +00:00
|
|
|
// Anything loaded?
|
2016-05-28 13:30:54 +00:00
|
|
|
switch (target_value.GetType())
|
|
|
|
{
|
|
|
|
case C4V_PropList:
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
C4PropList *props = target_value._getPropList();
|
|
|
|
// Headers
|
|
|
|
QModelIndex parent = index.parent();
|
|
|
|
if (!parent.isValid())
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
if (!index.column())
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
if (role == Qt::DisplayRole)
|
|
|
|
{
|
|
|
|
if (index.row() >= 0 && index.row() < property_groups.size())
|
|
|
|
return property_groups[index.row()].name;
|
|
|
|
}
|
|
|
|
else if (role == Qt::FontRole)
|
|
|
|
{
|
|
|
|
return header_font;
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
return QVariant();
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case C4V_Array:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return QVariant(); // invalid target
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
|
|
|
// Query latest data from prop list
|
2016-04-17 02:02:24 +00:00
|
|
|
Property *prop = static_cast<Property *>(index.internalPointer());
|
|
|
|
if (!prop) return QVariant();
|
|
|
|
if (!prop->delegate) prop->delegate = delegate_factory->GetDelegateByValue(prop->delegate_info);
|
2016-04-09 18:20:31 +00:00
|
|
|
if (role == Qt::DisplayRole)
|
2016-02-12 04:37:13 +00:00
|
|
|
{
|
|
|
|
switch (index.column())
|
|
|
|
{
|
|
|
|
case 0: // First col: Property Name
|
2016-04-17 02:02:24 +00:00
|
|
|
return QVariant(prop->display_name->GetCStr());
|
2016-02-12 04:37:13 +00:00
|
|
|
case 1: // Second col: Property value
|
|
|
|
{
|
|
|
|
C4Value v;
|
2016-05-28 13:30:54 +00:00
|
|
|
prop->delegate->GetPropertyValue(target_value, prop->key, index.row(), &v);
|
|
|
|
return QVariant(prop->delegate->GetDisplayString(v, target_value.getObj()));
|
2016-02-12 04:37:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-17 02:02:24 +00:00
|
|
|
else if (role == Qt::BackgroundColorRole && index.column()==1)
|
|
|
|
{
|
|
|
|
C4Value v;
|
2016-05-28 13:30:54 +00:00
|
|
|
prop->delegate->GetPropertyValue(target_value, prop->key, index.row(), &v);
|
|
|
|
QColor bgclr = prop->delegate->GetDisplayBackgroundColor(v, target_value.getObj());
|
2016-04-17 02:02:24 +00:00
|
|
|
if (bgclr.isValid()) return bgclr;
|
|
|
|
}
|
|
|
|
else if (role == Qt::TextColorRole && index.column() == 1)
|
|
|
|
{
|
|
|
|
C4Value v;
|
2016-05-28 13:30:54 +00:00
|
|
|
prop->delegate->GetPropertyValue(target_value, prop->key, index.row(), &v);
|
|
|
|
QColor txtclr = prop->delegate->GetDisplayTextColor(v, target_value.getObj());
|
2016-04-17 02:02:24 +00:00
|
|
|
if (txtclr.isValid()) return txtclr;
|
|
|
|
}
|
2016-02-12 04:37:13 +00:00
|
|
|
// Nothing to show
|
|
|
|
return QVariant();
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
|
|
|
|
QModelIndex C4ConsoleQtPropListModel::index(int row, int column, const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
if (column < 0 || column > 1) return QModelIndex();
|
2016-05-28 13:30:54 +00:00
|
|
|
switch (target_value.GetType())
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
case C4V_PropList:
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
// Top level index?
|
|
|
|
if (!parent.isValid())
|
|
|
|
{
|
|
|
|
// Top level has headers only
|
|
|
|
if (row < 0 || row >= property_groups.size()) return QModelIndex();
|
|
|
|
return createIndex(row, column, nullptr);
|
|
|
|
}
|
|
|
|
// Property?
|
|
|
|
QModelIndex grandparent = parent.parent();
|
|
|
|
if (!grandparent.isValid())
|
2016-04-09 18:20:31 +00:00
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
const PropertyGroup *property_group = NULL;
|
|
|
|
if (parent.row() >= 0 && parent.row() < property_groups.size())
|
|
|
|
{
|
|
|
|
property_group = &property_groups[parent.row()];
|
|
|
|
if (row < 0 || row >= property_group->props.size()) return QModelIndex();
|
|
|
|
const Property * prop = &(property_group->props[row]);
|
|
|
|
return createIndex(row, column, const_cast<Property *>(prop));
|
|
|
|
}
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
2016-05-28 13:30:54 +00:00
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
case C4V_Array:
|
|
|
|
{
|
|
|
|
if (parent.isValid()) return QModelIndex();
|
|
|
|
C4ValueArray *arr = target_value._getArray();
|
|
|
|
if (row < 0 || row >= arr->GetSize()) return QModelIndex();
|
|
|
|
if (!property_groups.size()) return QModelIndex();
|
|
|
|
const PropertyGroup *property_group = &property_groups[0];
|
|
|
|
// Ensure changes are reflected immediately
|
2016-06-03 05:20:43 +00:00
|
|
|
if (property_group->props.size() != arr->GetSize()) const_cast<C4ConsoleQtPropListModel *>(this)->UpdateValueArray(arr, nullptr, nullptr);
|
2016-05-28 13:30:54 +00:00
|
|
|
if (row < 0 || row >= property_group->props.size()) return QModelIndex();
|
|
|
|
const Property * prop = &(property_group->props[row]);
|
|
|
|
return createIndex(row, column, const_cast<Property *>(prop));
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return QModelIndex();
|
2016-04-09 18:20:31 +00:00
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex C4ConsoleQtPropListModel::parent(const QModelIndex &index) const
|
|
|
|
{
|
2016-05-28 13:30:54 +00:00
|
|
|
// Parent: Proplist only and only from 2nd level (properties) to 1st (groups)
|
|
|
|
C4PropList *props = target_value.getPropList();
|
|
|
|
if (props)
|
|
|
|
{
|
|
|
|
Property *prop = static_cast<Property *>(index.internalPointer());
|
|
|
|
if (!prop) return QModelIndex();
|
|
|
|
// Find list to use
|
|
|
|
return createIndex(prop->group_idx, 0, nullptr);
|
|
|
|
}
|
|
|
|
return QModelIndex();
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
Qt::ItemFlags flags = QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;
|
|
|
|
if (index.isValid() && index.internalPointer())
|
2016-04-04 03:46:58 +00:00
|
|
|
{
|
2016-06-03 05:20:43 +00:00
|
|
|
flags &= ~Qt::ItemIsDropEnabled; // only drop between the lines
|
2016-04-04 03:46:58 +00:00
|
|
|
Property *prop = static_cast<Property *>(index.internalPointer());
|
2016-06-03 05:20:43 +00:00
|
|
|
if (index.column() == 0)
|
|
|
|
{
|
|
|
|
// array elements can be re-arranged
|
|
|
|
if (prop->parent_value.GetType() == C4V_Array) flags |= Qt::ItemIsDragEnabled;
|
|
|
|
}
|
|
|
|
else if (index.column() == 1)
|
|
|
|
{
|
2016-06-06 05:54:05 +00:00
|
|
|
bool readonly = IsTargetReadonly();
|
2016-06-03 05:20:43 +00:00
|
|
|
// Only object properties are editable at the moment
|
|
|
|
if (!readonly)
|
|
|
|
flags |= Qt::ItemIsEditable;
|
|
|
|
else
|
|
|
|
flags &= ~Qt::ItemIsEnabled;
|
|
|
|
}
|
2016-04-04 03:46:58 +00:00
|
|
|
}
|
|
|
|
return flags;
|
2016-04-04 23:34:02 +00:00
|
|
|
}
|
2016-06-03 05:20:43 +00:00
|
|
|
|
|
|
|
Qt::DropActions C4ConsoleQtPropListModel::supportedDropActions() const
|
|
|
|
{
|
|
|
|
return Qt::MoveAction;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool C4ConsoleQtPropListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
|
|
|
{
|
|
|
|
// Drag+Drop movement on array only
|
|
|
|
if (action != Qt::MoveAction) return false;
|
|
|
|
C4ValueArray *arr = target_value.getArray();
|
|
|
|
if (!arr) return false;
|
|
|
|
if (!data->hasFormat("application/vnd.text")) return false;
|
|
|
|
if (row < 0) return false; // outside range: Could be above or below. Better don't drag at all.
|
|
|
|
// Decode indices of rows to move
|
|
|
|
QByteArray encodedData = data->data("application/vnd.text");
|
|
|
|
StdStrBuf rearrange_call;
|
|
|
|
rearrange_call.Format("MoveArrayItems(%%s, [%s], %d)", encodedData.constData(), row);
|
|
|
|
target_path.DoCall(rearrange_call.getData());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList C4ConsoleQtPropListModel::mimeTypes() const
|
|
|
|
{
|
|
|
|
QStringList types;
|
|
|
|
types << "application/vnd.text";
|
|
|
|
return types;
|
|
|
|
}
|
|
|
|
|
|
|
|
QMimeData *C4ConsoleQtPropListModel::mimeData(const QModelIndexList &indexes) const
|
|
|
|
{
|
|
|
|
// Add all moved indexes
|
|
|
|
QMimeData *mimeData = new QMimeData();
|
|
|
|
QByteArray encodedData;
|
|
|
|
int32_t count = 0;
|
|
|
|
for (const QModelIndex &index : indexes)
|
|
|
|
{
|
|
|
|
if (index.isValid() && !index.parent().isValid())
|
|
|
|
{
|
|
|
|
if (count) encodedData.append(",");
|
|
|
|
encodedData.append(QString::number(index.row()));
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mimeData->setData("application/vnd.text", encodedData);
|
|
|
|
return mimeData;
|
2016-06-06 05:54:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void C4ConsoleQtPropListModel::AddArrayElement()
|
|
|
|
{
|
|
|
|
C4Value new_val;
|
|
|
|
C4PropList *info_proplist = this->info_proplist.getPropList();
|
|
|
|
if (info_proplist) info_proplist->GetProperty(P_DefaultValue, &new_val);
|
|
|
|
target_path.DoCall(FormatString("PushBack(%%s, %s)", new_val.GetDataString(10).getData()).getData());
|
|
|
|
}
|
|
|
|
|
|
|
|
void C4ConsoleQtPropListModel::RemoveArrayElement()
|
|
|
|
{
|
|
|
|
// Compose script command to remove all selected array indices
|
|
|
|
StdStrBuf script;
|
|
|
|
for (QModelIndex idx : selection_model->selectedIndexes())
|
|
|
|
if (idx.isValid() && idx.column() == 0)
|
|
|
|
if (script.getLength())
|
|
|
|
script.AppendFormat(",%d", idx.row());
|
|
|
|
else
|
|
|
|
script.AppendFormat("%d", idx.row());
|
|
|
|
if (script.getLength()) target_path.DoCall(FormatString("RemoveArrayIndices(%%s, [%s])", script.getData()).getData());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool C4ConsoleQtPropListModel::IsTargetReadonly() const
|
|
|
|
{
|
|
|
|
if (target_path.IsEmpty()) return true;
|
|
|
|
switch (target_value.GetType())
|
|
|
|
{
|
|
|
|
case C4V_Array:
|
|
|
|
// Arrays are never frozen
|
|
|
|
return false;
|
|
|
|
case C4V_PropList:
|
|
|
|
{
|
|
|
|
C4PropList *parent_proplist = target_value._getPropList();
|
|
|
|
if (parent_proplist->IsFrozen()) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|