forked from Mirrors/openclonk
Qt Editor: Implement setting of properties
parent
e8f48fd53e
commit
98c36e5955
|
@ -85,6 +85,7 @@ public:
|
|||
void OnObjectSelectionChanged(class C4EditCursorSelection &selection); // selection changed (through other means than creator or object list view)
|
||||
bool CreateNewScenario(StdStrBuf *out_filename);
|
||||
void OnStartGame();
|
||||
void ClearGamePointers();
|
||||
|
||||
friend class C4ConsoleQtMainWindow;
|
||||
friend class C4ToolsDlg;
|
||||
|
|
|
@ -353,6 +353,11 @@ bool C4ConsoleGUI::CreateNewScenario(StdStrBuf *out_filename)
|
|||
state->SetObjectSelection(selection);
|
||||
}
|
||||
|
||||
void C4ConsoleGUI::ClearGamePointers()
|
||||
{
|
||||
state->ClearGamePointers();
|
||||
}
|
||||
|
||||
void C4ToolsDlg::UpdateToolCtrls()
|
||||
{
|
||||
// Set selected drawing tool
|
||||
|
|
|
@ -459,6 +459,12 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="propertyTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
|
|
|
@ -14,15 +14,439 @@
|
|||
* for the above references.
|
||||
*/
|
||||
|
||||
/* Proplist table view */
|
||||
|
||||
|
||||
#include <C4Include.h>
|
||||
#include <C4Value.h>
|
||||
#include <C4ConsoleQtPropListViewer.h>
|
||||
#include <C4Console.h>
|
||||
#include <C4Object.h>
|
||||
|
||||
/* Property path for property setting synchronization */
|
||||
|
||||
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);
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void C4PropertyPath::SetProperty(const char *set_string) const
|
||||
{
|
||||
// Compose script to update property
|
||||
StdStrBuf script;
|
||||
if (path_type != PPT_SetFunction)
|
||||
script.Format("%s=%s", path.getData(), set_string);
|
||||
else
|
||||
script.Format("%s(%s)", path.getData(), set_string);
|
||||
// Execute synced scripted
|
||||
// TODO: Use silent editor control later; for now it's good to have the output shown
|
||||
::Console.In(script.getData());
|
||||
}
|
||||
|
||||
void C4PropertyPath::SetProperty(const C4Value &to_val) const
|
||||
{
|
||||
SetProperty(to_val.GetDataString(9999999).getData());
|
||||
}
|
||||
|
||||
|
||||
/* Property editing */
|
||||
|
||||
void C4PropertyDelegate::UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
C4PropertyDelegateInt::C4PropertyDelegateInt(const C4PropertyDelegateFactory *factory, const C4PropList *props)
|
||||
: C4PropertyDelegate(factory)
|
||||
{
|
||||
// TODO min/max/step
|
||||
}
|
||||
|
||||
void C4PropertyDelegateInt::SetEditorData(QWidget *editor, const C4Value &val) const
|
||||
{
|
||||
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
|
||||
spinBox->setValue(val.getInt());
|
||||
}
|
||||
|
||||
void C4PropertyDelegateInt::SetModelData(QWidget *editor, const C4PropertyPath &property_path) const
|
||||
{
|
||||
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
|
||||
spinBox->interpretText();
|
||||
property_path.SetProperty(C4VInt(spinBox->value()));
|
||||
}
|
||||
|
||||
QWidget *C4PropertyDelegateInt::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const
|
||||
{
|
||||
QSpinBox *editor = new QSpinBox(parent);
|
||||
connect(editor, &QSpinBox::editingFinished, this, [editor, this]() {
|
||||
emit EditingDoneSignal(editor);
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnumEditor::UpdateOptionIndex(int idx)
|
||||
{
|
||||
if (!updating) parent_delegate->UpdateOptionIndex(this, idx);
|
||||
}
|
||||
|
||||
C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, int reserve_count)
|
||||
: C4PropertyDelegate(factory)
|
||||
{
|
||||
options.reserve(reserve_count);
|
||||
}
|
||||
|
||||
C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, const C4ValueArray &props)
|
||||
: C4PropertyDelegate(factory)
|
||||
{
|
||||
// Build enum options from C4Value definitions in script
|
||||
options.reserve(props.GetSize());
|
||||
for (int32_t i = 0; i < props.GetSize(); ++i)
|
||||
{
|
||||
const C4Value &v = props.GetItem(i);
|
||||
const C4PropList *props = v.getPropList();
|
||||
if (!props) continue;
|
||||
Option option;
|
||||
option.name = props->GetPropertyStr(P_Name);
|
||||
if (!option.name.Get()) option.name = ::Strings.RegString("???");
|
||||
option.value_key = props->GetPropertyStr(P_ValueKey);
|
||||
props->GetProperty(P_Value, &option.value);
|
||||
option.type = C4V_Type(props->GetPropertyInt(P_Type, C4V_Any));
|
||||
option.option_key = props->GetPropertyStr(P_OptionKey);
|
||||
// Derive storage type from given elements in delegate definition
|
||||
if (option.type != C4V_Any)
|
||||
option.storage_type = Option::StorageByType;
|
||||
else if (option.option_key.Get())
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int32_t C4PropertyDelegateEnum::GetOptionByValue(const C4Value &val) const
|
||||
{
|
||||
int32_t iopt = 0;
|
||||
for (auto &option : options)
|
||||
{
|
||||
bool match = false;
|
||||
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();
|
||||
if (props)
|
||||
{
|
||||
C4Value propval;
|
||||
props->GetPropertyByS(option.option_key.Get(), &propval);
|
||||
match = (val == propval);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
if (match) break;
|
||||
++iopt;
|
||||
}
|
||||
// If no option matches, just pick first
|
||||
return iopt % options.size();
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnum::UpdateEditorParameter(C4PropertyDelegateEnum::Editor *editor) const
|
||||
{
|
||||
// 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
|
||||
if (!option.adelegate && option.adelegate_val.GetType() != C4V_Nil)
|
||||
option.adelegate = factory->GetDelegateByValue(option.adelegate_val);
|
||||
// Create editor if needed
|
||||
if (option.adelegate)
|
||||
{
|
||||
// Determine value to be shown in editor
|
||||
C4Value parameter_val = editor->last_val;
|
||||
if (option.value_key.Get())
|
||||
{
|
||||
C4PropList *props = editor->last_val.getPropList();
|
||||
if (props) props->GetPropertyByS(option.value_key.Get(), ¶meter_val);
|
||||
}
|
||||
// Show it
|
||||
editor->parameter_widget = option.adelegate->CreateEditor(factory, editor, QStyleOptionViewItem());
|
||||
if (editor->parameter_widget)
|
||||
{
|
||||
editor->layout->addWidget(editor->parameter_widget);
|
||||
option.adelegate->SetEditorData(editor->parameter_widget, parameter_val);
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnum::SetEditorData(QWidget *aeditor, const C4Value &val) const
|
||||
{
|
||||
Editor *editor = static_cast<Editor*>(aeditor);
|
||||
editor->last_val = val;
|
||||
editor->updating = true;
|
||||
// Update option selection
|
||||
editor->option_box->setCurrentIndex(GetOptionByValue(val));
|
||||
// Update parameter
|
||||
UpdateEditorParameter(editor);
|
||||
editor->updating = false;
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnum::SetModelData(QWidget *aeditor, const C4PropertyPath &property_path) const
|
||||
{
|
||||
// Fetch value from editor
|
||||
Editor *editor = static_cast<Editor*>(aeditor);
|
||||
int32_t idx = editor->option_box->currentIndex();
|
||||
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)
|
||||
{
|
||||
// Value from a parameter?
|
||||
option.adelegate->SetModelData(editor->parameter_widget, use_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No parameter. Use value.
|
||||
use_path.SetProperty(option.value);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *C4PropertyDelegateEnum::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const
|
||||
{
|
||||
Editor *editor = new Editor(parent, this);
|
||||
editor->layout = new QHBoxLayout(editor);
|
||||
editor->layout->setContentsMargins(0, 0, 0, 0);
|
||||
editor->layout->setMargin(0);
|
||||
editor->layout->setSpacing(0);
|
||||
editor->updating = true;
|
||||
editor->option_box = new QComboBox(editor);
|
||||
editor->layout->addWidget(editor->option_box);
|
||||
for (auto &option : options) editor->option_box->addItem(option.name->GetData().getData());
|
||||
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;
|
||||
return editor;
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnum::UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *editor, int newval) const
|
||||
{
|
||||
UpdateEditorParameter(editor);
|
||||
emit EditorValueChangedSignal(editor);
|
||||
}
|
||||
|
||||
C4PropertyDelegateC4Value::C4PropertyDelegateC4Value(const C4PropertyDelegateFactory *factory)
|
||||
: C4PropertyDelegateEnum(factory, 10)
|
||||
{
|
||||
// Add default C4Value selections
|
||||
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")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Delegate factory: Create delegates based on the C4Value type */
|
||||
|
||||
C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByString(const C4String *str, const C4PropList *props) const
|
||||
{
|
||||
// safety
|
||||
if (!str) return NULL;
|
||||
// create default base types
|
||||
if (str->GetData() == "int") return new C4PropertyDelegateInt(this, props);
|
||||
if (str->GetData() == "any") return new C4PropertyDelegateC4Value(this);
|
||||
// unknown type
|
||||
return NULL;
|
||||
}
|
||||
|
||||
C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByValue(const C4Value &val) const
|
||||
{
|
||||
switch (val.GetType())
|
||||
{
|
||||
case C4V_Nil:
|
||||
return new C4PropertyDelegateC4Value(this);
|
||||
case C4V_Array:
|
||||
return new C4PropertyDelegateEnum(this, *val.getArray());
|
||||
case C4V_PropList:
|
||||
{
|
||||
C4PropList *props = val._getPropList();
|
||||
if (!props) break;
|
||||
return CreateDelegateByString(props->GetPropertyStr(P_Type), props);
|
||||
}
|
||||
case C4V_String:
|
||||
return CreateDelegateByString(val._getStr(), NULL);
|
||||
default:
|
||||
// Invalid delegte: No editor.
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
C4PropertyDelegate *C4PropertyDelegateFactory::GetDelegateByValue(const C4Value &val) const
|
||||
{
|
||||
auto iter = delegates.find(val);
|
||||
if (iter != delegates.end()) return iter->second.get();
|
||||
C4PropertyDelegate *new_delegate = CreateDelegateByValue(val);
|
||||
delegates.insert(std::make_pair(val, std::unique_ptr<C4PropertyDelegate>(new_delegate)));
|
||||
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);
|
||||
if (!d) return;
|
||||
// Fetch property only first time - ignore further updates to simplify editing
|
||||
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
||||
if (!prop || !prop->about_to_edit) return;
|
||||
prop->about_to_edit = false;
|
||||
C4Value val;
|
||||
C4PropList *props = prop->parent_proplist->getPropList();
|
||||
if (props)
|
||||
{
|
||||
props->GetPropertyByS(prop->name.Get(), &val);
|
||||
d->SetEditorData(editor, val);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (!d) return;
|
||||
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
|
||||
C4PropList *props = prop->parent_proplist->getPropList();
|
||||
if (props)
|
||||
{
|
||||
// Set value view path
|
||||
C4PropertyPath path(prop->parent_proplist->GetDataString().getData());
|
||||
C4PropertyPath subpath(path, prop->name->GetCStr());
|
||||
d->SetModelData(editor, subpath);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
QWidget *editor = d->CreateEditor(this, parent, option);
|
||||
// Connect value change signals
|
||||
// For some reason, commitData needs a non-const pointer
|
||||
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);
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
C4PropertyDelegate *d = GetDelegateByIndex(index);
|
||||
if (!d) return;
|
||||
return d->UpdateEditorGeometry(editor, option);
|
||||
}
|
||||
|
||||
|
||||
/* Proplist table view */
|
||||
|
||||
C4ConsoleQtPropListModel::C4ConsoleQtPropListModel()
|
||||
{
|
||||
proplist.reset(new C4Value());
|
||||
}
|
||||
|
||||
C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel()
|
||||
|
@ -32,21 +456,38 @@ C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel()
|
|||
void C4ConsoleQtPropListModel::SetPropList(class C4PropList *new_proplist)
|
||||
{
|
||||
// Update properties
|
||||
proplist->SetPropList(new_proplist);
|
||||
if (new_proplist) properties = new_proplist->GetSortedLocalProperties();
|
||||
QModelIndex topLeft = index(0, 0);
|
||||
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1);
|
||||
proplist.SetPropList(new_proplist);
|
||||
if (new_proplist)
|
||||
{
|
||||
auto new_properties = new_proplist->GetSortedLocalProperties();
|
||||
properties.resize(new_properties.size());
|
||||
for (int32_t i = 0; i < new_properties.size(); ++i)
|
||||
{
|
||||
properties[i].parent_proplist = &proplist;
|
||||
properties[i].name = new_properties[i];
|
||||
properties[i].delegate_info.Set0();
|
||||
properties[i].delegate = NULL; // init when needed
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
properties.clear();
|
||||
}
|
||||
QModelIndex topLeft = index(0, 0, QModelIndex());
|
||||
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1, QModelIndex());
|
||||
emit dataChanged(topLeft, bottomRight);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
int C4ConsoleQtPropListModel::rowCount(const QModelIndex & parent) const
|
||||
{
|
||||
if (parent.isValid()) return 0;
|
||||
return properties.size();
|
||||
}
|
||||
|
||||
int C4ConsoleQtPropListModel::columnCount(const QModelIndex & parent) const
|
||||
{
|
||||
if (parent.isValid()) return 0;
|
||||
return 2; // Name + Data
|
||||
}
|
||||
|
||||
|
@ -64,21 +505,19 @@ QVariant C4ConsoleQtPropListModel::headerData(int section, Qt::Orientation orien
|
|||
QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) const
|
||||
{
|
||||
// Query latest data from prop list
|
||||
C4PropList *props = proplist->getPropList();
|
||||
C4PropList *props = proplist.getPropList();
|
||||
if (role == Qt::DisplayRole && props)
|
||||
{
|
||||
int row = index.row();
|
||||
if (row < 0 || row >= properties.size()) return QVariant();
|
||||
C4String *prop_name = properties[row].Get();
|
||||
if (!prop_name) return QVariant();
|
||||
Property *prop = static_cast<Property *>(index.internalPointer());
|
||||
if (!prop) return QVariant();
|
||||
switch (index.column())
|
||||
{
|
||||
case 0: // First col: Property Name
|
||||
return QVariant(prop_name->GetCStr());
|
||||
return QVariant(prop->name->GetCStr());
|
||||
case 1: // Second col: Property value
|
||||
{
|
||||
C4Value v;
|
||||
if (!props->GetPropertyByS(prop_name, &v)) return QVariant("???"); /* Property got removed between update calls */
|
||||
if (!props->GetPropertyByS(prop->name, &v)) return QVariant("???"); /* Property got removed between update calls */
|
||||
return QVariant(v.GetDataString().getData());
|
||||
}
|
||||
}
|
||||
|
@ -86,3 +525,35 @@ QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) con
|
|||
// Nothing to show
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex C4ConsoleQtPropListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
// Flat model
|
||||
if (parent.isValid()) return QModelIndex();
|
||||
// In range?
|
||||
if (column < 0 || column > 1) return QModelIndex();
|
||||
if (row < 0 || row >= properties.size()) return QModelIndex();
|
||||
const Property * prop = &properties[row];
|
||||
return createIndex(row, column, const_cast<Property *>(prop));
|
||||
}
|
||||
|
||||
QModelIndex C4ConsoleQtPropListModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
|
||||
if (index.isValid() && index.column() == 1 && index.internalPointer())
|
||||
{
|
||||
Property *prop = static_cast<Property *>(index.internalPointer());
|
||||
C4PropList *parent_proplist = prop->parent_proplist->getPropList();
|
||||
// Only object properties are editable at the moment
|
||||
if (parent_proplist && !parent_proplist->IsFrozen() && (parent_proplist->GetObject()==parent_proplist))
|
||||
flags |= Qt::ItemIsEditable;
|
||||
else
|
||||
flags &= ~Qt::ItemIsEnabled;
|
||||
}
|
||||
return flags;
|
||||
}
|
|
@ -23,14 +23,186 @@
|
|||
#include <C4Include.h> // needed for automoc
|
||||
#include <C4ConsoleGUI.h> // for glew.h
|
||||
#include <C4ConsoleQt.h>
|
||||
#include <C4Value.h>
|
||||
|
||||
// Prop list view implemented as a model view
|
||||
class C4ConsoleQtPropListModel : public QAbstractTableModel
|
||||
// Path to a property, like e.g. Object(123).foo.bar[456].baz
|
||||
// Used to allow proper synchronization of property setting
|
||||
class C4PropertyPath
|
||||
{
|
||||
// TODO: For now just storing the path. May want to keep the path info later to allow validation/updating of values
|
||||
StdCopyStrBuf path;
|
||||
|
||||
enum PathType
|
||||
{
|
||||
PPT_Root = 0,
|
||||
PPT_Property = 1,
|
||||
PPT_Index = 2,
|
||||
PPT_SetFunction = 3,
|
||||
} path_type;
|
||||
public:
|
||||
C4PropertyPath() {}
|
||||
C4PropertyPath(const char *path) : path(path), path_type(PPT_Root) {}
|
||||
C4PropertyPath(const C4PropertyPath &parent, int32_t elem_index);
|
||||
C4PropertyPath(const C4PropertyPath &parent, const char *child_property, PathType path_type = PPT_Property);
|
||||
const char *GetPath() const { return path.getData(); }
|
||||
|
||||
void SetProperty(const char *set_string) const;
|
||||
void SetProperty(const C4Value &to_val) const;
|
||||
};
|
||||
|
||||
class C4PropertyDelegate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<class C4Value> proplist;
|
||||
std::vector< C4RefCntPointer<C4String> > properties;
|
||||
protected:
|
||||
const class C4PropertyDelegateFactory *factory;
|
||||
|
||||
public:
|
||||
C4PropertyDelegate(const class C4PropertyDelegateFactory *factory)
|
||||
: factory(factory) { }
|
||||
virtual ~C4PropertyDelegate() { }
|
||||
|
||||
virtual void SetEditorData(QWidget *editor, const C4Value &val) const = 0;
|
||||
virtual void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const = 0;
|
||||
virtual QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const = 0;
|
||||
virtual void UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const;
|
||||
|
||||
signals:
|
||||
void EditorValueChangedSignal(QWidget *editor) const;
|
||||
void EditingDoneSignal(QWidget *editor) const;
|
||||
};
|
||||
|
||||
class C4PropertyDelegateInt : public C4PropertyDelegate
|
||||
{
|
||||
public:
|
||||
C4PropertyDelegateInt(const class C4PropertyDelegateFactory *factory, const C4PropList *props=NULL);
|
||||
|
||||
void SetEditorData(QWidget *editor, const C4Value &val) const override;
|
||||
void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override;
|
||||
QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override;
|
||||
};
|
||||
|
||||
// Widget holder class
|
||||
class C4PropertyDelegateEnumEditor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
const class C4PropertyDelegateEnum *parent_delegate;
|
||||
C4Value last_val;
|
||||
QComboBox *option_box;
|
||||
QHBoxLayout *layout;
|
||||
QWidget *parameter_widget;
|
||||
bool updating;
|
||||
|
||||
C4PropertyDelegateEnumEditor(QWidget *parent, const class C4PropertyDelegateEnum *parent_delegate)
|
||||
: QWidget(parent), parent_delegate(parent_delegate), option_box(NULL), layout(NULL), parameter_widget(NULL), updating(false) { }
|
||||
public slots :
|
||||
void UpdateOptionIndex(int idx);
|
||||
};
|
||||
|
||||
class C4PropertyDelegateEnum : public C4PropertyDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef C4PropertyDelegateEnumEditor Editor; // qmake doesn't like nested classes
|
||||
|
||||
struct Option
|
||||
{
|
||||
C4RefCntPointer<C4String> name; // Display name in Editor enum dropdown box
|
||||
C4RefCntPointer<C4String> option_key;
|
||||
C4RefCntPointer<C4String> value_key;
|
||||
C4V_Type type; // Assume this option is set when value is of given type
|
||||
C4Value value; // Value to set if this entry is selected
|
||||
mutable C4PropertyDelegate *adelegate; // Delegate to display if this entry is selected (pointer owned by C4PropertyDelegateFactory)
|
||||
C4Value adelegate_val; // Value to resolve adelegate from
|
||||
// How the currently selected option is identified from the value
|
||||
enum StorageType {
|
||||
StorageNone=0, // Invalid option
|
||||
StorageByType=1, // Use type to identify this enum
|
||||
StorageByValue=2, // This option sets a constant value
|
||||
StorageByKey=3, // Assume value is a proplist; identify option by field option_key
|
||||
} storage_type;
|
||||
|
||||
Option() : type(C4V_Any), adelegate(NULL), storage_type(StorageNone) {}
|
||||
};
|
||||
private:
|
||||
std::vector<Option> options;
|
||||
public:
|
||||
C4PropertyDelegateEnum(const class C4PropertyDelegateFactory *factory, int reserve_count = 0);
|
||||
C4PropertyDelegateEnum(const class C4PropertyDelegateFactory *factory, const C4ValueArray &props);
|
||||
|
||||
void AddTypeOption(C4String *name, C4V_Type type, const C4Value &val, C4PropertyDelegate *adelegate=NULL);
|
||||
|
||||
void SetEditorData(QWidget *editor, const C4Value &val) const override;
|
||||
void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override;
|
||||
QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const override;
|
||||
void UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const override;
|
||||
|
||||
private:
|
||||
int32_t GetOptionByValue(const C4Value &val) const;
|
||||
void UpdateEditorParameter(C4PropertyDelegateEnum::Editor *editor) const;
|
||||
|
||||
public slots:
|
||||
void UpdateOptionIndex(Editor *editor, int idx) const;
|
||||
};
|
||||
|
||||
class C4PropertyDelegateC4Value : public C4PropertyDelegateEnum
|
||||
{
|
||||
public:
|
||||
C4PropertyDelegateC4Value(const C4PropertyDelegateFactory *factory);
|
||||
};
|
||||
|
||||
class C4PropertyDelegateFactory : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
mutable std::map<C4Value, std::unique_ptr<C4PropertyDelegate> > delegates;
|
||||
|
||||
C4PropertyDelegate *CreateDelegateByString(const C4String *str, const C4PropList *props=NULL) const;
|
||||
C4PropertyDelegate *CreateDelegateByValue(const C4Value &val) const;
|
||||
C4PropertyDelegate *GetDelegateByIndex(const QModelIndex &index) const;
|
||||
public:
|
||||
C4PropertyDelegateFactory() { }
|
||||
~C4PropertyDelegateFactory() { }
|
||||
|
||||
C4PropertyDelegate *GetDelegateByValue(const C4Value &val) const;
|
||||
|
||||
void ClearDelegates();
|
||||
|
||||
private:
|
||||
void EditorValueChanged(QWidget *editor);
|
||||
void EditingDone(QWidget *editor);
|
||||
|
||||
protected:
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
};
|
||||
|
||||
// Prop list view implemented as a model view
|
||||
class C4ConsoleQtPropListModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Property
|
||||
{
|
||||
C4PropertyPath property_path;
|
||||
C4Value *parent_proplist;
|
||||
C4RefCntPointer<C4String> name;
|
||||
C4Value delegate_info;
|
||||
C4PropertyDelegate *delegate;
|
||||
bool about_to_edit;
|
||||
|
||||
Property() : parent_proplist(NULL), delegate(NULL), about_to_edit(false) {}
|
||||
};
|
||||
private:
|
||||
C4Value proplist;
|
||||
std::vector< Property > properties;
|
||||
public:
|
||||
C4ConsoleQtPropListModel();
|
||||
~C4ConsoleQtPropListModel();
|
||||
|
@ -42,6 +214,9 @@ protected:
|
|||
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
#endif // WITH_QT_EDITOR
|
||||
|
|
|
@ -491,7 +491,12 @@ bool C4ConsoleGUIState::CreateConsoleWindow(C4AbstractApp *app)
|
|||
ui.creatorTreeView->setModel(definition_list_model.get());
|
||||
window->connect(ui.creatorTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, window.get(), &C4ConsoleQtMainWindow::OnCreatorSelectionChanged);
|
||||
window->connect(ui.creatorTreeView->selectionModel(), &QItemSelectionModel::currentChanged, window.get(), &C4ConsoleQtMainWindow::OnCreatorCurrentChanged);
|
||||
|
||||
|
||||
// Property editor
|
||||
property_delegate_factory.reset(new C4PropertyDelegateFactory());
|
||||
ui.propertyTable->setItemDelegateForColumn(1, property_delegate_factory.get());
|
||||
ui.propertyTable->verticalHeader()->setDefaultSectionSize(ui.propertyTable->fontMetrics().height()+4);
|
||||
|
||||
// Welcome page
|
||||
InitWelcomeScreen();
|
||||
ShowWelcomeScreen();
|
||||
|
@ -672,6 +677,7 @@ void C4ConsoleGUIState::PropertyDlgUpdate(C4EditCursorSelection &rSelection, boo
|
|||
if (sel_count != 1)
|
||||
{
|
||||
// Multi object selection: Hide property view; show info label
|
||||
property_model->SetPropList(NULL);
|
||||
ui.propertyTable->setVisible(false);
|
||||
ui.selectionInfoLabel->setText(rSelection.GetDataString().getData());
|
||||
}
|
||||
|
@ -862,3 +868,8 @@ void C4ConsoleGUIState::HideWelcomeScreen()
|
|||
{
|
||||
ui.welcomeDockWidget->close();
|
||||
}
|
||||
|
||||
void C4ConsoleGUIState::ClearGamePointers()
|
||||
{
|
||||
if (property_delegate_factory) property_delegate_factory->ClearDelegates();
|
||||
}
|
||||
|
|
|
@ -147,6 +147,7 @@ public:
|
|||
std::unique_ptr<QApplication> application;
|
||||
std::unique_ptr<C4ConsoleQtMainWindow> window;
|
||||
std::unique_ptr<class C4ConsoleQtPropListModel> property_model;
|
||||
std::unique_ptr<class C4PropertyDelegateFactory> property_delegate_factory;
|
||||
std::unique_ptr<class C4ConsoleQtObjectListModel> object_list_model;
|
||||
std::unique_ptr<class C4ConsoleQtDefinitionListModel> definition_list_model;
|
||||
std::list<class C4ConsoleQtViewportDockWidget *> viewports;
|
||||
|
@ -216,6 +217,8 @@ public:
|
|||
void InitWelcomeScreen();
|
||||
void ShowWelcomeScreen();
|
||||
void HideWelcomeScreen();
|
||||
|
||||
void ClearGamePointers();
|
||||
};
|
||||
|
||||
class C4ConsoleGUI::State : public C4ConsoleGUIState
|
||||
|
|
|
@ -762,6 +762,7 @@ void C4EditCursor::Clear()
|
|||
ObjselectDelItems();
|
||||
#endif
|
||||
selection.clear();
|
||||
Console.PropertyDlgUpdate(selection, false);
|
||||
creator_overlay.reset(NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -599,6 +599,12 @@ void C4Game::Clear()
|
|||
::FontLoader.Clear();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_QT_EDITOR
|
||||
// clear console pointers held into script engine
|
||||
::Console.EditCursor.Clear();
|
||||
::Console.ClearGamePointers();
|
||||
#endif
|
||||
|
||||
C4PropListNumbered::ClearShelve(); // may be nonempty if there was a fatal error during section load
|
||||
ScriptEngine.Clear();
|
||||
// delete any remaining prop lists from circular chains
|
||||
|
|
|
@ -459,11 +459,11 @@ void C4PropList::AppendDataString(StdStrBuf * out, const char * delim, int depth
|
|||
}
|
||||
}
|
||||
|
||||
std::vector< C4RefCntPointer<C4String> > C4PropList::GetSortedLocalProperties() const
|
||||
std::vector< C4String * > C4PropList::GetSortedLocalProperties() const
|
||||
{
|
||||
// return property list without descending into prototype
|
||||
std::list<const C4Property *> sorted_props = Properties.GetSortedListOfElementPointers();
|
||||
std::vector< C4RefCntPointer<C4String> > result;
|
||||
std::vector< C4String * > result;
|
||||
result.reserve(sorted_props.size() + 1);
|
||||
result.push_back(&::Strings.P[P_Prototype]); // implicit prototype for every prop list
|
||||
for (auto p : sorted_props) result.push_back(p->Key);
|
||||
|
|
|
@ -133,7 +133,7 @@ public:
|
|||
|
||||
void CompileFunc(StdCompiler *pComp, C4ValueNumbers *);
|
||||
void AppendDataString(StdStrBuf * out, const char * delim, int depth = 3) const;
|
||||
std::vector< C4RefCntPointer<C4String> > GetSortedLocalProperties() const;
|
||||
std::vector< C4String * > GetSortedLocalProperties() const;
|
||||
|
||||
bool operator==(const C4PropList &b) const;
|
||||
#ifdef _DEBUG
|
||||
|
|
|
@ -246,6 +246,10 @@ C4StringTable::C4StringTable()
|
|||
P[P_MusicBreakChance] = "MusicBreakChance";
|
||||
P[P_MusicMaxPositionMemory] = "MusicMaxPositionMemory";
|
||||
P[P_InflameLandscape] = "InflameLandscape";
|
||||
P[P_OptionKey] = "OptionKey";
|
||||
P[P_ValueKey] = "ValueKey";
|
||||
P[P_Value] = "Value";
|
||||
P[P_Delegate] = "Delegate";
|
||||
P[DFA_WALK] = "WALK";
|
||||
P[DFA_FLIGHT] = "FLIGHT";
|
||||
P[DFA_KNEEL] = "KNEEL";
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
p = r.p;
|
||||
r.p = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
~C4RefCntPointer() { DecRef(); }
|
||||
|
@ -454,6 +455,10 @@ enum C4PropertyName
|
|||
P_MusicBreakChance,
|
||||
P_MusicMaxPositionMemory,
|
||||
P_InflameLandscape,
|
||||
P_OptionKey,
|
||||
P_ValueKey,
|
||||
P_Value,
|
||||
P_Delegate,
|
||||
// Default Action Procedures
|
||||
DFA_WALK,
|
||||
DFA_FLIGHT,
|
||||
|
|
|
@ -561,6 +561,18 @@ bool C4Value::operator != (const C4Value& Value2) const
|
|||
return !(*this == Value2);
|
||||
}
|
||||
|
||||
C4V_Type C4Value::GetTypeEx() const
|
||||
{
|
||||
// Return type including types derived from prop list types (such as C4V_Def)
|
||||
if (Type == C4V_PropList)
|
||||
{
|
||||
if (FnCnvEffect()) return C4V_Effect;
|
||||
if (FnCnvObject()) return C4V_Object;
|
||||
if (FnCnvDef()) return C4V_Def;
|
||||
}
|
||||
return Type;
|
||||
}
|
||||
|
||||
void C4Value::LogDeletedObjectWarning(C4PropList * p)
|
||||
{
|
||||
if (p->GetPropListNumbered())
|
||||
|
|
|
@ -156,6 +156,7 @@ public:
|
|||
// getters
|
||||
C4V_Data GetData() const { return Data; }
|
||||
C4V_Type GetType() const { return Type; }
|
||||
C4V_Type GetTypeEx() const; // Return type including types derived from prop list types (such as C4V_Def)
|
||||
|
||||
const char *GetTypeName() const { return GetC4VName(GetType()); }
|
||||
|
||||
|
|
Loading…
Reference in New Issue