From 2a2fc68e3fc247d238d14690e55e5f6ebc215558 Mon Sep 17 00:00:00 2001 From: Sven Eberhardt Date: Sat, 9 Apr 2016 14:20:31 -0400 Subject: [PATCH] Qt Editor: Add user properties --- src/editor/C4ConsoleQtMainWindow.ui | 14 +- src/editor/C4ConsoleQtObjectListViewer.cpp | 3 +- src/editor/C4ConsoleQtPropListViewer.cpp | 263 ++++++++++++++++----- src/editor/C4ConsoleQtPropListViewer.h | 43 +++- src/editor/C4ConsoleQtState.cpp | 25 +- src/game/C4Application.cpp | 3 + src/game/C4Application.h | 3 + src/object/C4DefList.cpp | 19 ++ src/object/C4DefList.h | 1 + src/script/C4PropList.cpp | 22 ++ src/script/C4PropList.h | 1 + src/script/C4StringTable.cpp | 10 +- src/script/C4StringTable.h | 21 +- 13 files changed, 343 insertions(+), 85 deletions(-) diff --git a/src/editor/C4ConsoleQtMainWindow.ui b/src/editor/C4ConsoleQtMainWindow.ui index 3cd691ebb..838303767 100644 --- a/src/editor/C4ConsoleQtMainWindow.ui +++ b/src/editor/C4ConsoleQtMainWindow.ui @@ -449,6 +449,12 @@ + + + 300 + 0 + + SELECTION @@ -458,16 +464,16 @@ - + QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked QAbstractItemView::SelectRows - - false - + + 9 + diff --git a/src/editor/C4ConsoleQtObjectListViewer.cpp b/src/editor/C4ConsoleQtObjectListViewer.cpp index e0b9be105..f8c9b0fdc 100644 --- a/src/editor/C4ConsoleQtObjectListViewer.cpp +++ b/src/editor/C4ConsoleQtObjectListViewer.cpp @@ -53,7 +53,8 @@ void C4ConsoleQtObjectListModel::Invalidate() void C4ConsoleQtObjectListModel::OnItemRemoved(C4PropList *p) { - for (auto idx : this->persistentIndexList()) + QModelIndexList list = this->persistentIndexList(); + for (auto idx : list) if (idx.internalPointer() == p) this->changePersistentIndex(idx, QModelIndex()); Invalidate(); diff --git a/src/editor/C4ConsoleQtPropListViewer.cpp b/src/editor/C4ConsoleQtPropListViewer.cpp index 5d39163a8..91bad7bd1 100644 --- a/src/editor/C4ConsoleQtPropListViewer.cpp +++ b/src/editor/C4ConsoleQtPropListViewer.cpp @@ -19,6 +19,8 @@ #include "editor/C4ConsoleQtPropListViewer.h" #include "editor/C4Console.h" #include "object/C4Object.h" +#include "object/C4DefList.h" +#include "object/C4Def.h" /* Property path for property setting synchronization */ @@ -62,15 +64,28 @@ void C4PropertyPath::SetProperty(const C4Value &to_val) const /* Property editing */ +C4PropertyDelegate::C4PropertyDelegate(const C4PropertyDelegateFactory *factory, const C4PropList *props) + : QObject(), factory(factory) +{ + // Resolve setter callback name + if (props) set_function = props->GetPropertyStr(P_Set); +} + void C4PropertyDelegate::UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const { editor->setGeometry(option.rect); } C4PropertyDelegateInt::C4PropertyDelegateInt(const C4PropertyDelegateFactory *factory, const C4PropList *props) - : C4PropertyDelegate(factory) + : C4PropertyDelegate(factory, props), min(std::numeric_limits::min()), max(std::numeric_limits::max()), step(1) { // TODO min/max/step + if (props) + { + min = props->GetPropertyInt(P_Min, min); + max = props->GetPropertyInt(P_Max, max); + step = props->GetPropertyInt(P_Step, step); + } } void C4PropertyDelegateInt::SetEditorData(QWidget *editor, const C4Value &val) const @@ -89,48 +104,54 @@ void C4PropertyDelegateInt::SetModelData(QWidget *editor, const C4PropertyPath & QWidget *C4PropertyDelegateInt::CreateEditor(const C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const { QSpinBox *editor = new QSpinBox(parent); + editor->setMinimum(min); + editor->setMaximum(max); + editor->setSingleStep(step); connect(editor, &QSpinBox::editingFinished, this, [editor, this]() { emit EditingDoneSignal(editor); }); return editor; } -C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, int reserve_count) - : C4PropertyDelegate(factory) -{ - options.reserve(reserve_count); -} - -C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, const C4ValueArray &props) - : C4PropertyDelegate(factory) +C4PropertyDelegateEnum::C4PropertyDelegateEnum(const C4PropertyDelegateFactory *factory, const C4PropList *props, const C4ValueArray *poptions) + : C4PropertyDelegate(factory, props) { // Build enum options from C4Value definitions in script - options.reserve(props.GetSize()); - for (int32_t i = 0; i < props.GetSize(); ++i) + if (!poptions && props) poptions = props->GetPropertyArray(P_Options); + if (poptions) { - 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); + options.reserve(poptions->GetSize()); + for (int32_t i = 0; i < poptions->GetSize(); ++i) + { + const C4Value &v = poptions->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::ReserveOptions(int32_t num) +{ + options.reserve(num); +} + void C4PropertyDelegateEnum::AddTypeOption(C4String *name, C4V_Type type, const C4Value &val, C4PropertyDelegate *adelegate) { Option option; @@ -142,6 +163,15 @@ void C4PropertyDelegateEnum::AddTypeOption(C4String *name, C4V_Type type, const options.push_back(option); } +void C4PropertyDelegateEnum::AddConstOption(C4String *name, const C4Value &val) +{ + Option option; + option.name = name; + option.value = val; + option.storage_type = Option::StorageByValue; + options.push_back(option); +} + int32_t C4PropertyDelegateEnum::GetOptionByValue(const C4Value &val) const { int32_t iopt = 0; @@ -249,7 +279,10 @@ void C4PropertyDelegateEnum::SetModelData(QWidget *aeditor, const C4PropertyPath // Value from a parameter or directly from the enum? if (option.adelegate) { - // Value from a parameter? + // Value from a parameter. + // Using a setter function? + if (option.adelegate->GetSetFunction()) + use_path = C4PropertyPath(use_path, option.adelegate->GetSetFunction(), C4PropertyPath::PPT_SetFunction); option.adelegate->SetModelData(editor->parameter_widget, use_path); } else @@ -283,10 +316,29 @@ void C4PropertyDelegateEnum::UpdateOptionIndex(C4PropertyDelegateEnum::Editor *e emit EditorValueChangedSignal(editor); } -C4PropertyDelegateC4ValueEnum::C4PropertyDelegateC4ValueEnum(const C4PropertyDelegateFactory *factory) - : C4PropertyDelegateEnum(factory, 10) +C4PropertyDelegateDef::C4PropertyDelegateDef(const C4PropertyDelegateFactory *factory, const C4PropList *props) + : C4PropertyDelegateEnum(factory, props) +{ + // Collect sorted definitions + std::vector defs = ::Definitions.GetAllDefs(props ? props->GetPropertyStr(P_Filter) : NULL); + 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 option_name = ::Strings.RegString(FormatString("%s (%s)", def->id.ToString(), def->GetName())); + AddConstOption(option_name, C4Value(def)); + } +} + +C4PropertyDelegateC4ValueEnum::C4PropertyDelegateC4ValueEnum(const C4PropertyDelegateFactory *factory, const C4PropList *props) + : C4PropertyDelegateEnum(factory, props) { // Add default C4Value selections + ReserveOptions(10); 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"))); @@ -349,8 +401,9 @@ C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByString(const C4St if (!str) return NULL; // create default base types if (str->GetData() == "int") return new C4PropertyDelegateInt(this, props); - if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this); - if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this); + if (str->GetData() == "def") return new C4PropertyDelegateDef(this, props); + if (str->GetData() == "c4valueenum") return new C4PropertyDelegateC4ValueEnum(this, props); + if (str->GetData() == "any") return new C4PropertyDelegateC4ValueInput(this, props); // unknown type return NULL; } @@ -360,9 +413,9 @@ C4PropertyDelegate *C4PropertyDelegateFactory::CreateDelegateByValue(const C4Val switch (val.GetType()) { case C4V_Nil: - return new C4PropertyDelegateC4ValueInput(this); + return new C4PropertyDelegateC4ValueInput(this, NULL); case C4V_Array: - return new C4PropertyDelegateEnum(this, *val.getArray()); + return new C4PropertyDelegateEnum(this, NULL, val.getArray()); case C4V_PropList: { C4PropList *props = val._getPropList(); @@ -438,9 +491,13 @@ void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel C4PropList *props = prop->parent_proplist->getPropList(); if (props) { - // Set value view path + // Compose set command C4PropertyPath path(prop->parent_proplist->GetDataString().getData()); - C4PropertyPath subpath(path, prop->name->GetCStr()); + C4PropertyPath subpath; + if (d->GetSetFunction()) + subpath = C4PropertyPath(path, d->GetSetFunction(), C4PropertyPath::PPT_SetFunction); + else + subpath = C4PropertyPath(path, prop->name->GetCStr()); d->SetModelData(editor, subpath); } } @@ -470,11 +527,18 @@ void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QSty return d->UpdateEditorGeometry(editor, option); } +QSize C4PropertyDelegateFactory::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int height = QApplication::fontMetrics().height() + 4; + return QSize(100, height); +} + /* Proplist table view */ C4ConsoleQtPropListModel::C4ConsoleQtPropListModel() { + header_font.setBold(true); } C4ConsoleQtPropListModel::~C4ConsoleQtPropListModel() @@ -487,19 +551,50 @@ void C4ConsoleQtPropListModel::SetPropList(class C4PropList *new_proplist) proplist.SetPropList(new_proplist); if (new_proplist) { + // Always: Internal properties auto new_properties = new_proplist->GetSortedLocalProperties(); - properties.resize(new_properties.size()); + internal_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 + internal_properties[i].parent_proplist = &proplist; + internal_properties[i].name = new_properties[i]; + internal_properties[i].delegate_info.Set0(); // default C4Value delegate + internal_properties[i].delegate = NULL; // init when needed + internal_properties[i].is_internal = true; + } + // Objects only: Published properties + if (new_proplist->GetObject()) + { + const char *editor_prop_prefix = "EditorProp_"; + auto new_properties = new_proplist->GetSortedProperties(editor_prop_prefix); + published_properties.resize(new_properties.size()); + for (int32_t i = 0; i < new_properties.size(); ++i) + { + published_properties[i].parent_proplist = &proplist; + published_properties[i].name = NULL; + published_properties[i].delegate_info.Set0(); // default C4Value delegate + published_properties[i].delegate = NULL; // init when needed + published_properties[i].is_internal = false; + C4Value published_prop_val; + new_proplist->GetPropertyByS(new_properties[i], &published_prop_val); + C4PropList *published_prop = published_prop_val.getPropList(); + if (published_prop) + { + published_properties[i].name = published_prop->GetPropertyStr(P_Name); + published_properties[i].delegate_info.SetPropList(published_prop); + } + if (!published_properties[i].name) published_properties[i].name = ::Strings.RegString(new_properties[i]->GetCStr() + strlen(editor_prop_prefix)); + } + } + else + { + published_properties.clear(); } } else { - properties.clear(); + internal_properties.clear(); + published_properties.clear(); } QModelIndex topLeft = index(0, 0, QModelIndex()); QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1, QModelIndex()); @@ -509,13 +604,22 @@ void C4ConsoleQtPropListModel::SetPropList(class C4PropList *new_proplist) int C4ConsoleQtPropListModel::rowCount(const QModelIndex & parent) const { - if (parent.isValid()) return 0; - return properties.size(); + // Nothing loaded? + if (!proplist.getPropList()) return 0; + // Top level: Published and internal properties + if (!parent.isValid()) return 2; + // Mid level: Descend into property lists + QModelIndex grandparent = parent.parent(); + if (!grandparent.isValid()) + { + if (parent.row() == 0) return published_properties.size(); + if (parent.row() == 1) return internal_properties.size(); + } + return 0; } int C4ConsoleQtPropListModel::columnCount(const QModelIndex & parent) const { - if (parent.isValid()) return 0; return 2; // Name + Data } @@ -525,16 +629,36 @@ QVariant C4ConsoleQtPropListModel::headerData(int section, Qt::Orientation orien if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { if (section == 0) return QVariant(LoadResStr("IDS_CTL_NAME")); - if (section == 1) return QVariant(LoadResStr("IDS_CTL_VALUE")); + if (section == 1) return QVariant(LoadResStr("IDS_CNS_VALUE")); } return QVariant(); } QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) const { - // Query latest data from prop list + // Anything loaded? C4PropList *props = proplist.getPropList(); - if (role == Qt::DisplayRole && props) + if (!props) return QVariant(); + // Headers + QModelIndex parent = index.parent(); + if (!parent.isValid()) + { + if (!index.column()) + { + if (role == Qt::DisplayRole) + { + if (index.row() == 0) return QVariant(LoadResStr("IDS_CNS_PROPERTIES")); + if (index.row() == 1) return QVariant(LoadResStr("IDS_CNS_INTERNAL")); + } + else if (role == Qt::FontRole) + { + return header_font; + } + } + return QVariant(); + } + // Query latest data from prop list + if (role == Qt::DisplayRole) { Property *prop = static_cast(index.internalPointer()); if (!prop) return QVariant(); @@ -545,7 +669,7 @@ QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) con case 1: // Second col: Property value { C4Value v; - if (!props->GetPropertyByS(prop->name, &v)) return QVariant("???"); /* Property got removed between update calls */ + props->GetPropertyByS(prop->name, &v); // Just display nil if property is not set return QVariant(v.GetDataString().getData()); } } @@ -556,18 +680,37 @@ QVariant C4ConsoleQtPropListModel::data(const QModelIndex & index, int role) con 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(prop)); + // Top level index? + if (!parent.isValid()) + { + // Top level has headers only + if (row == 0 || row == 1) return createIndex(row, column, NULL); + return QModelIndex(); + } + // Property? + QModelIndex grandparent = parent.parent(); + if (!grandparent.isValid()) + { + const std::vector< Property > *property_list = NULL; + if (parent.row() == 0) property_list = &published_properties; + if (parent.row() == 1) property_list = &internal_properties; + if (property_list) + { + if (row < 0 || row >= property_list->size()) return QModelIndex(); + const Property * prop = &(*property_list)[row]; + return createIndex(row, column, const_cast(prop)); + } + } + return QModelIndex(); } QModelIndex C4ConsoleQtPropListModel::parent(const QModelIndex &index) const { - return QModelIndex(); + Property *prop = static_cast(index.internalPointer()); + if (!prop) return QModelIndex(); + // Find list to use + return createIndex(prop->is_internal ? 1 : 0, 0, NULL); } Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const diff --git a/src/editor/C4ConsoleQtPropListViewer.h b/src/editor/C4ConsoleQtPropListViewer.h index 4ac0dbf3c..1a0b67a27 100644 --- a/src/editor/C4ConsoleQtPropListViewer.h +++ b/src/editor/C4ConsoleQtPropListViewer.h @@ -32,6 +32,7 @@ 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; +public: enum PathType { PPT_Root = 0, @@ -56,10 +57,10 @@ class C4PropertyDelegate : public QObject protected: const class C4PropertyDelegateFactory *factory; + C4RefCntPointer set_function; public: - C4PropertyDelegate(const class C4PropertyDelegateFactory *factory) - : factory(factory) { } + C4PropertyDelegate(const class C4PropertyDelegateFactory *factory, const C4PropList *props); virtual ~C4PropertyDelegate() { } virtual void SetEditorData(QWidget *editor, const C4Value &val) const = 0; @@ -67,6 +68,8 @@ public: virtual QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option) const = 0; virtual void UpdateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option) const; + const char *GetSetFunction() const { return set_function.Get() ? set_function->GetCStr() : NULL; } // get name of setter function for this property + signals: void EditorValueChangedSignal(QWidget *editor) const; void EditingDoneSignal(QWidget *editor) const; @@ -74,8 +77,10 @@ signals: class C4PropertyDelegateInt : public C4PropertyDelegate { +private: + int32_t min, max, step; public: - C4PropertyDelegateInt(const class C4PropertyDelegateFactory *factory, const C4PropList *props=NULL); + C4PropertyDelegateInt(const class C4PropertyDelegateFactory *factory, const C4PropList *props); void SetEditorData(QWidget *editor, const C4Value &val) const override; void SetModelData(QWidget *editor, const C4PropertyPath &property_path) const override; @@ -105,8 +110,9 @@ class C4PropertyDelegateEnum : public C4PropertyDelegate public: typedef C4PropertyDelegateEnumEditor Editor; // qmake doesn't like nested classes - struct Option + class Option { + public: C4RefCntPointer name; // Display name in Editor enum dropdown box C4RefCntPointer option_key; C4RefCntPointer value_key; @@ -126,11 +132,14 @@ public: }; private: std::vector