Qt Editor: Improve proplist and array display/editing

* Add/Remove element buttons
* Display customization of user delegates
qteditor
Sven Eberhardt 2016-06-06 01:54:05 -04:00
parent 4d04135cda
commit 16e31098b4
10 changed files with 253 additions and 18 deletions

View File

@ -223,3 +223,14 @@ global func MoveArrayItems(array arr, array source_indices, int insert_before)
}
return true;
}
// Deletes multiple indexes from an array, does not change the order of items in the array.
global func RemoveArrayIndices(array arr, array indices)
{
indices = indices[:];
SortArray(indices, true);
for (idx in indices)
if (idx < GetLength(arr))
RemoveArrayIndex(arr, idx);
return true;
}

View File

@ -32,6 +32,8 @@ IDS_BTN_YES=Ja
IDS_CHAT_NOTCONNECTED=nicht verbunden
IDS_CHAT_SERVER=Server
IDS_CNS_ACTION=Aktivität:
IDS_CNS_ARRAYADD=Element hinzufügen
IDS_CNS_ARRAYREMOVE=Element entfernen
IDS_CNS_ARRAYSHORT=[%1 Elemente]
IDS_CNS_BACK=<< Zurück
IDS_CNS_BROWSE=Durchsuchen...

View File

@ -32,6 +32,8 @@ IDS_BTN_YES=Yes
IDS_CHAT_NOTCONNECTED=not connected
IDS_CHAT_SERVER=Server
IDS_CNS_ACTION=Action:
IDS_CNS_ARRAYADD=Add item
IDS_CNS_ARRAYREMOVE=Remove item
IDS_CNS_ARRAYSHORT=[%1 items]
IDS_CNS_BACK=<< Back
IDS_CNS_BROWSE=Browse...

View File

@ -483,6 +483,27 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="arrayEditingLayout">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="arrayAddButton">
<property name="text">
<string comment="res">IDS_CNS_ARRAYADD</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="arrayRemoveButton">
<property name="text">
<string comment="res">IDS_CNS_ARRAYREMOVE</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="propertyTable">
<property name="acceptDrops">
@ -1445,6 +1466,38 @@
</hint>
</hints>
</connection>
<connection>
<sender>arrayAddButton</sender>
<signal>pressed()</signal>
<receiver>MainWindow</receiver>
<slot>AddArrayElement()</slot>
<hints>
<hint type="sourcelabel">
<x>756</x>
<y>116</y>
</hint>
<hint type="destinationlabel">
<x>477</x>
<y>312</y>
</hint>
</hints>
</connection>
<connection>
<sender>arrayRemoveButton</sender>
<signal>pressed()</signal>
<receiver>MainWindow</receiver>
<slot>RemoveArrayElement()</slot>
<hints>
<hint type="sourcelabel">
<x>886</x>
<y>116</y>
</hint>
<hint type="destinationlabel">
<x>477</x>
<y>312</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>PlayPressed(bool)</slot>
@ -1478,5 +1531,7 @@
<slot>FileNew()</slot>
<slot>WelcomeLinkActivated(QString)</slot>
<slot>AscendPropertyPath()</slot>
<slot>AddArrayElement()</slot>
<slot>RemoveArrayElement()</slot>
</slots>
</ui>

View File

@ -250,7 +250,7 @@ C4PropertyDelegateDescendPath::C4PropertyDelegateDescendPath(const class C4Prope
edit_on_selection = props->GetPropertyBool(P_EditOnSelection, edit_on_selection);
}
}
void C4PropertyDelegateDescendPath::SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const
{
Editor *editor = static_cast<Editor *>(aeditor);
@ -282,16 +282,72 @@ QWidget *C4PropertyDelegateDescendPath::CreateEditor(const class C4PropertyDeleg
return peditor.release();
}
QString C4PropertyDelegateDescendPath::GetDisplayString(const C4Value &v, C4Object *obj) const
C4PropertyDelegateArray::C4PropertyDelegateArray(const class C4PropertyDelegateFactory *factory, C4PropList *props)
: C4PropertyDelegateDescendPath(factory, props), max_array_display(0), element_delegate(nullptr)
{
switch (v.GetType())
if (props)
{
case C4V_PropList: return QString("{...}");
case C4V_Array: return QString(LoadResStr("IDS_CNS_ARRAYSHORT")).arg(uint32_t(v.getInt()));
default: return QString(LoadResStr("IDS_CNS_INVALID"));
max_array_display = props->GetPropertyInt(P_Display);
}
}
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;
}
C4PropertyDelegateColor::C4PropertyDelegateColor(const class C4PropertyDelegateFactory *factory, C4PropList *props)
: C4PropertyDelegate(factory, props)
@ -832,8 +888,8 @@ 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() == "array") return new C4PropertyDelegateDescendPath(this, props);
if (str->GetData() == "proplist") return new C4PropertyDelegateDescendPath(this, props);
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);
@ -906,7 +962,7 @@ void C4PropertyDelegateFactory::setEditorData(QWidget *editor, const QModelIndex
{
// Put property value from proplist into editor
C4PropertyDelegate *d = GetDelegateByIndex(index);
if (!d) return;
if (!CheckCurrentEditor(d, editor)) return;
// Fetch property only first time - ignore further updates to the same value to simplify editing
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
if (!prop) return;
@ -922,7 +978,7 @@ void C4PropertyDelegateFactory::setModelData(QWidget *editor, QAbstractItemModel
{
// Fetch property value from editor and set it into proplist
C4PropertyDelegate *d = GetDelegateByIndex(index);
if (!d) return;
if (!CheckCurrentEditor(d, editor)) return;
C4ConsoleQtPropListModel::Property *prop = static_cast<C4ConsoleQtPropListModel::Property *>(index.internalPointer());
SetPropertyData(d, editor, prop);
}
@ -952,19 +1008,24 @@ QWidget *C4PropertyDelegateFactory::createEditor(QWidget *parent, const QStyleOp
});
}
current_editor = editor;
current_editor_delegate = d;
return editor;
}
void C4PropertyDelegateFactory::destroyEditor(QWidget *editor, const QModelIndex &index) const
{
if (editor == current_editor) current_editor = nullptr;
if (editor == current_editor)
{
current_editor = nullptr;
current_editor_delegate = nullptr;
}
QStyledItemDelegate::destroyEditor(editor, index);
}
void C4PropertyDelegateFactory::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
C4PropertyDelegate *d = GetDelegateByIndex(index);
if (!d) return;
if (!CheckCurrentEditor(d, editor)) return;
return d->UpdateEditorGeometry(editor, option);
}
@ -995,6 +1056,17 @@ void C4PropertyDelegateFactory::OnPropListChanged()
if (current_editor) emit closeEditor(current_editor);
}
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;
}
/* Proplist table view */
@ -1458,10 +1530,7 @@ Qt::ItemFlags C4ConsoleQtPropListModel::flags(const QModelIndex &index) const
}
else if (index.column() == 1)
{
bool readonly = false;
C4PropList *parent_proplist = prop->parent_value.getPropList();
if (parent_proplist && parent_proplist->IsFrozen()) readonly = true;
if (target_path.IsEmpty()) readonly = true;
bool readonly = IsTargetReadonly();
// Only object properties are editable at the moment
if (!readonly)
flags |= Qt::ItemIsEditable;
@ -1517,4 +1586,44 @@ QMimeData *C4ConsoleQtPropListModel::mimeData(const QModelIndexList &indexes) co
}
mimeData->setData("application/vnd.text", encodedData);
return mimeData;
}
}
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;
}
}

View File

@ -126,7 +126,7 @@ public:
class C4PropertyDelegateDescendPath : public C4PropertyDelegate
{
private:
protected:
C4Value info_proplist;
bool edit_on_selection;
public:
@ -136,6 +136,26 @@ public:
void SetEditorData(QWidget *aeditor, const C4Value &val, const C4PropertyPath &property_path) const override;
QWidget *CreateEditor(const class C4PropertyDelegateFactory *parent_delegate, QWidget *parent, const QStyleOptionViewItem &option, bool by_selection) const override;
};
class C4PropertyDelegateArray : public C4PropertyDelegateDescendPath
{
private:
int32_t max_array_display;
mutable C4PropertyDelegate *element_delegate; // lazy eval
public:
C4PropertyDelegateArray(const class C4PropertyDelegateFactory *factory, C4PropList *props);
QString GetDisplayString(const C4Value &v, C4Object *obj) const override;
};
class C4PropertyDelegatePropList : public C4PropertyDelegateDescendPath
{
private:
C4RefCntPointer<C4String> display_string;
public:
C4PropertyDelegatePropList(const class C4PropertyDelegateFactory *factory, C4PropList *props);
QString GetDisplayString(const C4Value &v, C4Object *obj) const override;
};
@ -308,6 +328,7 @@ class C4PropertyDelegateFactory : public QStyledItemDelegate
mutable std::map<C4Value, std::unique_ptr<C4PropertyDelegate> > delegates;
mutable QWidget *current_editor;
mutable C4PropertyDelegate *current_editor_delegate;
mutable C4Value last_edited_value;
class C4ConsoleQtPropListModel *property_model;
@ -325,6 +346,7 @@ public:
void SetPropertyModel(class C4ConsoleQtPropListModel *new_property_model) { property_model = new_property_model; }
class C4ConsoleQtPropListModel *GetPropertyModel() const { return property_model; }
void OnPropListChanged();
bool CheckCurrentEditor(C4PropertyDelegate *d, QWidget *editor) const;
private:
void EditorValueChanged(QWidget *editor);
@ -422,6 +444,10 @@ public:
class C4PropList *GetBasePropList() const { return base_proplist.getPropList(); }
int32_t GetTargetPathStackSize() const { return target_path_stack.size(); }
const char *GetTargetPathText() const { return target_path.GetPath(); }
bool IsArray() const { return !!target_value.getArray(); }
void AddArrayElement();
void RemoveArrayElement();
bool IsTargetReadonly() const;
public:
int rowCount(const QModelIndex & parent = QModelIndex()) const override;

View File

@ -316,6 +316,17 @@ void C4ConsoleQtMainWindow::AscendPropertyPath()
::Console.EditCursor.InvalidateSelection();
}
void C4ConsoleQtMainWindow::AddArrayElement()
{
if (state->property_model) state->property_model->AddArrayElement();
}
void C4ConsoleQtMainWindow::RemoveArrayElement()
{
if (state->property_model) state->property_model->RemoveArrayElement();
}
bool C4ConsoleQtMainWindow::HandleEditorKeyDown(QKeyEvent *event)
{
switch (event->key())
@ -508,6 +519,9 @@ 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);
window->connect(ui.propertyTable->selectionModel(), &QItemSelectionModel::currentChanged, window.get(), [this]() {
this->ui.arrayRemoveButton->setDisabled(this->property_model->IsTargetReadonly() || this->ui.propertyTable->selectionModel()->selectedRows().empty());
});
// Welcome page
InitWelcomeScreen();
@ -688,6 +702,7 @@ void C4ConsoleGUIState::SetInputFunctions(std::list<const char*> &functions)
void C4ConsoleGUIState::PropertyDlgUpdate(C4EditCursorSelection &rSelection, bool force_function_update)
{
int sel_count = rSelection.size();
bool is_array = false;
if (sel_count != 1)
{
// Multi object selection: Hide property view; show info label
@ -713,9 +728,18 @@ void C4ConsoleGUIState::PropertyDlgUpdate(C4EditCursorSelection &rSelection, boo
}
ui.selectionInfoLabel->setText(property_model->GetTargetPathText());
ui.propertyEditAscendPathButton->setVisible(property_model->GetTargetPathStackSize() >= 1);
is_array = property_model->IsArray();
if (is_array)
{
bool is_readonly = property_model->IsTargetReadonly();
ui.arrayAddButton->setDisabled(is_readonly);
ui.arrayRemoveButton->setDisabled(is_readonly || ui.propertyTable->selectionModel()->selectedRows().empty());
}
ui.propertyTable->setEnabled(true);
::Console.EditCursor.ValidateSelection();
}
ui.arrayAddButton->setVisible(is_array);
ui.arrayRemoveButton->setVisible(is_array);
// Function update in script combo box
if (force_function_update)
{

View File

@ -130,6 +130,8 @@ public slots:
void OnCreatorCurrentChanged(const QModelIndex & current, const QModelIndex & previous);
void OnObjectListSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected);
void AscendPropertyPath();
void AddArrayElement();
void RemoveArrayElement();
// Global editor key processing
bool HandleEditorKeyDown(QKeyEvent *event);
bool HandleEditorKeyUp(QKeyEvent *event);

View File

@ -266,6 +266,8 @@ C4StringTable::C4StringTable()
P[P_EditOnSelection] = "EditOnSelection";
P[P_DefaultEditorProp] = "DefaultEditorProp";
P[P_CopyDefault] = "CopyDefault";
P[P_Display] = "Display";
P[P_DefaultValue] = "DefaultValue";
P[DFA_WALK] = "WALK";
P[DFA_FLIGHT] = "FLIGHT";
P[DFA_KNEEL] = "KNEEL";

View File

@ -490,6 +490,8 @@ enum C4PropertyName
P_EditOnSelection,
P_DefaultEditorProp,
P_CopyDefault,
P_Display,
P_DefaultValue,
// Default Action Procedures
DFA_WALK,
DFA_FLIGHT,