Qt Editor: Implement setting of properties

qteditor
Sven Eberhardt 2016-04-03 23:46:58 -04:00
parent e8f48fd53e
commit 98c36e5955
15 changed files with 722 additions and 21 deletions

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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(), &parameter_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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -762,6 +762,7 @@ void C4EditCursor::Clear()
ObjselectDelItems();
#endif
selection.clear();
Console.PropertyDlgUpdate(selection, false);
creator_overlay.reset(NULL);
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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";

View File

@ -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,

View File

@ -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())

View File

@ -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()); }