1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
4 #include "ObjectStructureWidget.h"
6 #include "ScriptBrowserUtils.h"
7 #include "PropertiesWidget.h"
8 #include "NameEditorWidget.h"
10 #include "StateItem.h"
12 #include "ObjectModel.h"
15 #include <QFilteringPanel.h>
16 #include <QAdvancedPropertyTree.h>
17 #include <ProxyModels/AttributeFilterProxyModel.h>
18 #include <Controls/QPopupWidget.h>
19 #include <Controls/DictionaryWidget.h>
20 #include <EditorFramework/BroadcastManager.h>
21 #include <EditorFramework/Inspector.h>
22 #include <ICommandManager.h>
24 #include <QAbstractItemModel>
25 #include <QStyledItemDelegate>
26 #include <QVBoxLayout>
27 #include <QAdvancedTreeView.h>
31 #include <QSizePolicy>
32 #include <QToolButton>
35 #include <QItemSelection>
37 #include <QVariantMap>
38 #include <QCollapsibleFrame.h>
40 namespace CrySchematycEditor
{
42 class CObjectStructureModel
: public QAbstractItemModel
54 Role_Display
= Qt::DisplayRole
,
55 Role_ToolTip
= Qt::ToolTipRole
,
56 Role_Icon
= Qt::DecorationRole
,
57 Role_Pointer
= Qt::UserRole
61 CObjectStructureModel(CAbstractObjectStructureModel
& model
)
64 m_model
.SignalObjectStructureItemAdded
.Connect(this, &CObjectStructureModel::OnItemAdded
);
65 m_model
.SignalObjectStructureItemRemoved
.Connect(this, &CObjectStructureModel::OnItemRemoved
);
66 m_model
.SignalObjectStructureItemInvalidated
.Connect(this, &CObjectStructureModel::OnItemInvalidated
);
69 ~CObjectStructureModel()
71 m_model
.SignalObjectStructureItemAdded
.DisconnectObject(this);
72 m_model
.SignalObjectStructureItemRemoved
.DisconnectObject(this);
73 m_model
.SignalObjectStructureItemInvalidated
.DisconnectObject(this);
77 virtual int rowCount(const QModelIndex
& parent
) const override
79 if (!parent
.isValid())
81 return m_model
.GetGraphItemCount() + m_model
.GetStateMachineItemCount();
85 const CAbstractObjectStructureModelItem
* pItem
= static_cast<const CAbstractObjectStructureModelItem
*>(parent
.internalPointer());
88 return pItem
->GetNumChildItems();
94 virtual int columnCount(const QModelIndex
& parent
) const override
99 virtual QVariant
headerData(int section
, Qt::Orientation orientation
, int role
) const override
101 if (orientation
== Qt::Horizontal
&& role
== Role_Display
)
106 return QString("Name");
114 virtual QVariant
data(const QModelIndex
& index
, int role
) const override
118 const CAbstractObjectStructureModelItem
* pItem
= static_cast<const CAbstractObjectStructureModelItem
*>(index
.internalPointer());
125 if (index
.column() == Column_Name
)
127 if (!pItem
->GetName().isEmpty())
128 return pItem
->GetName();
134 const QString text
= pItem
->GetToolTip();
141 return reinterpret_cast<quintptr
>(pItem
);
145 if (index
.column() == Column_Name
)
147 const CryIcon
* pIcon
= pItem
->GetIcon();
148 if (pIcon
&& !pIcon
->isNull())
149 return *pItem
->GetIcon();
161 virtual Qt::ItemFlags
flags(const QModelIndex
& index
) const override
163 Qt::ItemFlags flags
= QAbstractItemModel::flags(index
);
165 if (index
.isValid() && index
.column() == Column_Name
)
167 CAbstractObjectStructureModelItem
* pItem
= static_cast<CAbstractObjectStructureModelItem
*>(index
.internalPointer());
168 if (pItem
&& pItem
->AllowsRenaming())
170 flags
|= Qt::ItemIsEditable
;
174 flags
&= ~Qt::ItemIsEditable
;
180 virtual QModelIndex
index(int row
, int column
, const QModelIndex
& parent
= QModelIndex()) const override
182 if (!parent
.isValid())
184 CAbstractObjectStructureModelItem
* pItem
= m_model
.GetChildItemByIndex(row
);
185 return QAbstractItemModel::createIndex(row
, column
, reinterpret_cast<quintptr
>(pItem
));
189 CAbstractObjectStructureModelItem
* pItem
= static_cast<CAbstractObjectStructureModelItem
*>(parent
.internalPointer());
192 return QAbstractItemModel::createIndex(row
, column
, reinterpret_cast<quintptr
>(pItem
->GetChildItemByIndex(row
)));
196 return QModelIndex();
199 virtual QModelIndex
parent(const QModelIndex
& index
) const override
203 const CAbstractObjectStructureModelItem
* pItem
= static_cast<const CAbstractObjectStructureModelItem
*>(index
.internalPointer());
206 const CAbstractObjectStructureModelItem
* pParentItem
= pItem
->GetParentItem();
209 const CAbstractObjectStructureModelItem
* pParentParentItem
= pParentItem
->GetParentItem();
210 if (pParentParentItem
)
212 for (int32 i
= 0; i
< pParentParentItem
->GetNumChildItems(); ++i
)
214 if (pParentParentItem
->GetChildItemByIndex(i
) == pParentItem
)
216 return QAbstractItemModel::createIndex(i
, 0, reinterpret_cast<quintptr
>(pParentItem
));
222 for (int32 i
= 0; i
< m_model
.GetNumItems(); ++i
)
224 if (m_model
.GetChildItemByIndex(i
) == pParentItem
)
226 return QAbstractItemModel::createIndex(i
, 0, reinterpret_cast<quintptr
>(pParentItem
));
234 return QModelIndex();
237 virtual bool setData(const QModelIndex
& index
, const QVariant
& value
, int role
= Qt::EditRole
) override
239 if (role
== Qt::EditRole
)
243 switch (index
.column())
247 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.internalPointer());
250 const QString newName
= value
.value
<QString
>();
251 if (pItem
->GetName() != newName
)
252 pItem
->SetName(newName
);
266 // ~QAbstractItemModel
268 QModelIndex
GetIndexForItem(const CAbstractObjectStructureModelItem
& item
, int column
= 0) const
270 //const uint32 row = item.GetIndex();
272 /*std::function<QModelIndex(const CAbstractObjectStructureModelItem&, int)> GetIndexRecursive;
273 GetIndexRecursive = [&GetIndexRecursive, this](const CAbstractObjectStructureModelItem& item, int column) -> QModelIndex
275 const CAbstractObjectStructureModelItem* pParentItem = item.GetParentItem();
278 const uint32 idx = item.GetIndex();
279 QModelIndex parent = GetIndexRecursive(*pParentItem, column);
281 return index(idx, column, parent);
284 const uint32 idx = item.GetIndex();
285 return index(idx, column, QModelIndex());
288 const CAbstractObjectStructureModelItem
* pParentItem
= item
.GetParentItem();
291 const uint32 idx
= item
.GetIndex();
292 QModelIndex parent
= GetIndexForItem(*pParentItem
, column
);
294 return index(idx
, column
, parent
);
297 const uint32 idx
= item
.GetIndex();
298 return index(idx
, column
, QModelIndex());
300 //return GetIndexRecursive(item, column);
303 void OnItemAdded(CAbstractObjectStructureModelItem
& item
)
305 const uint32 index
= item
.GetIndex();
308 if (CAbstractObjectStructureModelItem
* pParentItem
= item
.GetParentItem())
310 parent
= GetIndexForItem(*pParentItem
);
312 QAbstractItemModel::beginInsertRows(parent
, index
, index
);
313 QAbstractItemModel::endInsertRows();
315 // Note: We need to call dataChanged here otherwise it's not possible to edit the item directly after inserting it.
316 const QModelIndex rowLeftCol
= GetIndexForItem(item
, 0);
317 const QModelIndex rowRightCol
= GetIndexForItem(item
, Column_Count
- 1);
320 roles
.push_back(Qt::EditRole
);
321 QAbstractItemModel::dataChanged(rowLeftCol
, rowRightCol
);
324 void OnItemRemoved(CAbstractObjectStructureModelItem
& item
)
326 const uint32 index
= item
.GetIndex();
329 if (CAbstractObjectStructureModelItem
* pParentItem
= item
.GetParentItem())
331 parent
= GetIndexForItem(*pParentItem
);
333 QAbstractItemModel::beginRemoveRows(parent
, index
, index
);
334 QAbstractItemModel::endRemoveRows();
337 void OnItemInvalidated(CAbstractObjectStructureModelItem
& item
)
339 const uint32 index
= item
.GetIndex();
342 if (CAbstractObjectStructureModelItem
* pParentItem
= item
.GetParentItem())
344 parent
= GetIndexForItem(*pParentItem
);
347 const QModelIndex rowLeftCol
= GetIndexForItem(item
, 0);
348 const QModelIndex rowRightCol
= GetIndexForItem(item
, Column_Count
- 1);
351 roles
.push_back(Qt::EditRole
);
352 QAbstractItemModel::dataChanged(rowLeftCol
, rowRightCol
);
356 CAbstractObjectStructureModel
& m_model
;
359 class CObjectItemDelegate
: public QStyledItemDelegate
361 typedef CNameEditor
<CAbstractObjectStructureModelItem
> EditorWidget
;
363 CObjectItemDelegate(QObject
* pParent
= nullptr)
364 : QStyledItemDelegate(pParent
)
366 m_pLabel
= new QLabel();
367 m_pLabel
->setWordWrap(true);
368 m_pToolTip
= new QPopupWidget("StructureItemTooltip", m_pLabel
);
369 m_pToolTip
->setAttribute(Qt::WA_ShowWithoutActivating
);
372 QWidget
* createEditor(QWidget
* parent
, const QStyleOptionViewItem
& option
, const QModelIndex
& index
) const override
374 if (index
.column() == CObjectStructureModel::Column_Name
)
376 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
379 EditorWidget
* pEditor
= new EditorWidget(*pItem
, parent
);
384 return QStyledItemDelegate::createEditor(parent
, option
, index
);
387 virtual bool helpEvent(QHelpEvent
* pEvent
, QAbstractItemView
* pView
, const QStyleOptionViewItem
& option
, const QModelIndex
& index
) override
389 QString description
= index
.data(CObjectStructureModel::Role_ToolTip
).value
<QString
>();
390 if (!description
.isEmpty())
392 if (m_currentDisplayedIndex
!= index
)
394 m_pLabel
->setText(description
);
396 if (m_pToolTip
->isHidden())
397 m_pToolTip
->ShowAt(pEvent
->globalPos());
399 m_pToolTip
->move(pEvent
->globalPos());
401 else if (m_pToolTip
->isHidden())
402 m_pToolTip
->ShowAt(pEvent
->globalPos());
404 m_currentDisplayedIndex
= index
;
414 QModelIndex m_currentDisplayedIndex
;
415 QPopupWidget
* m_pToolTip
;
419 CGraphsWidget::CGraphsWidget(QWidget
* pParent
)
423 , m_pComponentsList(nullptr)
424 , m_pFilterProxy(nullptr)
425 , m_pDataModel(nullptr)
427 setContentsMargins(0, 0, 0, 0);
429 QVBoxLayout
* pLayout
= new QVBoxLayout();
430 pLayout
->setContentsMargins(0, 0, 0, 0);
432 QLabel
* pTitle
= new QLabel("Functions & States");
433 QFont font
= pTitle
->font();
435 pTitle
->setFont(font
);
437 QWidget
* pSpacer
= new QWidget();
438 pSpacer
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
440 m_pAddButton
= new QToolButton();
441 m_pAddButton
->setText("+");
443 QToolBar
* pToolBar
= new QToolBar();
444 pToolBar
->addWidget(pTitle
);
445 pToolBar
->addWidget(pSpacer
);
446 pToolBar
->addWidget(m_pAddButton
);
448 m_pComponentsList
= new QAdvancedTreeView();
449 m_pComponentsList
->setContextMenuPolicy(Qt::CustomContextMenu
);
450 m_pComponentsList
->setSelectionMode(QAbstractItemView::SingleSelection
);
451 m_pComponentsList
->setSelectionBehavior(QAbstractItemView::SelectRows
);
452 m_pComponentsList
->setItemDelegate(new CObjectItemDelegate());
453 m_pComponentsList
->setHeaderHidden(true);
454 m_pComponentsList
->setMouseTracking(true);
455 m_pComponentsList
->setItemsExpandable(true);
456 m_pComponentsList
->setRootIsDecorated(true);
457 m_pComponentsList
->setTreePosition(static_cast<int>(CObjectStructureModel::Column_Name
));
458 m_pComponentsList
->setEditTriggers(QTreeView::DoubleClicked
| QTreeView::EditKeyPressed
);
459 m_pComponentsList
->sortByColumn(static_cast<int>(CObjectStructureModel::Column_Name
), Qt::AscendingOrder
);
461 m_pFilter
= new QFilteringPanel("Schematyc Function and States", nullptr);
462 m_pFilter
->SetContent(m_pComponentsList
);
463 m_pFilter
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
465 pLayout
->addWidget(pToolBar
);
466 pLayout
->addWidget(m_pFilter
);
467 pLayout
->addWidget(m_pComponentsList
);
469 QObject::connect(m_pComponentsList
, &QTreeView::clicked
, this, &CGraphsWidget::OnClicked
);
470 QObject::connect(m_pComponentsList
, &QTreeView::doubleClicked
, this, &CGraphsWidget::OnDoubleClicked
);
471 QObject::connect(m_pComponentsList
, &QTreeView::customContextMenuRequested
, this, &CGraphsWidget::OnContextMenu
);
473 QObject::connect(m_pAddButton
, &QToolButton::clicked
, this, &CGraphsWidget::OnAddPressed
);
477 m_pContextMenuContent
= new CDictionaryWidget();
478 //QObject::connect(m_pContextMenuContent, &CDictionaryWidget::OnEntryClicked, this, &CObjectStructureWidget::OnAddComponent);
480 m_pContextMenu
= new QPopupWidget("Add Component", m_pContextMenuContent
, QSize(250, 400), true);
483 CGraphsWidget::~CGraphsWidget()
487 // TODO: This is safe but not sure if required!
488 m_pFilterProxy
->setSourceModel(nullptr);
490 m_pFilterProxy
->deleteLater();
495 m_pDataModel
->deleteLater();
498 delete m_pContextMenu
;
501 void CGraphsWidget::SetModel(CAbstractObjectStructureModel
* pModel
)
503 if (m_pModel
!= pModel
)
508 m_pDataModel
= new CObjectStructureModel(*pModel
);
509 m_pFilterProxy
= new QAttributeFilterProxyModel(QAttributeFilterProxyModel::AcceptIfChildMatches
);
510 m_pFilterProxy
->setSourceModel(m_pDataModel
);
511 m_pFilterProxy
->setFilterKeyColumn(static_cast<int>(CObjectStructureModel::Column_Name
));
513 m_pFilter
->SetModel(m_pFilterProxy
);
514 m_pComponentsList
->setModel(m_pFilterProxy
);
515 QObject::connect(m_pComponentsList
->selectionModel(), &QItemSelectionModel::selectionChanged
, this, &CGraphsWidget::OnSelectionChanged
);
519 CObjectStructureModel
* pComponentsModel
= static_cast<CObjectStructureModel
*>(m_pFilterProxy
->sourceModel());
520 delete pComponentsModel
;
522 m_pFilterProxy
->setSourceModel(nullptr);
523 delete m_pFilterProxy
;
524 m_pFilterProxy
= nullptr;
525 m_pDataModel
= nullptr;
527 m_pFilter
->SetModel(nullptr);
528 m_pComponentsList
->setModel(nullptr);
533 void CGraphsWidget::OnClicked(const QModelIndex
& index
)
537 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
540 SignalEntryClicked(*pItem
);
545 void CGraphsWidget::OnDoubleClicked(const QModelIndex
& index
)
549 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
552 SignalEntryDoubleClicked(*pItem
);
557 void CGraphsWidget::OnSelectionChanged(const QItemSelection
& selected
, const QItemSelection
& deselected
)
559 QModelIndexList selectedIndexes
= selected
.indexes();
560 if (selectedIndexes
.count())
562 int32 row
= selectedIndexes
.at(0).row();
563 for (QModelIndex index
: selectedIndexes
)
565 if (row
== index
.row())
574 QModelIndex index
= selectedIndexes
.at(0);
575 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
577 if (CBroadcastManager
* pBroadcastManager
= CBroadcastManager::Get(this))
579 CPropertiesWidget
* pPropertiesWidget
= nullptr /*new CPropertiesWidget(*pItem)*/;
580 PopulateInspectorEvent
popEvent([pPropertiesWidget
](CInspector
& inspector
)
582 QCollapsibleFrame
* pInspectorWidget
= new QCollapsibleFrame("Properties");
583 pInspectorWidget
->SetWidget(pPropertiesWidget
);
584 inspector
.AddWidget(pInspectorWidget
);
587 pBroadcastManager
->Broadcast(popEvent
);
589 if (pItem
->GetType() == eObjectItemType_State
)
591 CStateItem
* pStateItem
= static_cast<CStateItem
*>(pItem
);
593 /*QVariantMap params;
594 params.insert("Model", reinterpret_cast<quintptr>(static_cast<CAbstractVariablesModel*>(pStateItem)));
596 // TODO: Remove hardcoded event name!
597 CustomEditorEvent editorEvent("PopulateVariablesWidget", params);
599 pBroadcastManager->Broadcast(editorEvent);*/
603 SignalEntrySelected(*pItem
);
608 QModelIndexList deselectedIndexes
= deselected
.indexes();
609 if (deselectedIndexes
.count())
611 if (CBroadcastManager
* pBroadcastManager
= CBroadcastManager::Get(this))
613 for (QModelIndex index
: selectedIndexes
)
615 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
616 if (pItem
->GetType() == eObjectItemType_State
)
618 // TODO: Remove hardcoded event name!
619 CustomEditorEvent
editorEvent("ClearVariablesWidget");
621 pBroadcastManager
->Broadcast(editorEvent
);
629 void CGraphsWidget::OnContextMenu(const QPoint
& point
)
631 const QPoint menuPos
= m_pComponentsList
->viewport()->mapToGlobal(point
);
633 const QModelIndex index
= m_pComponentsList
->indexAt(point
);
636 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
639 ICommandManager
* pCommandManager
= GetIEditor()->GetICommandManager();
643 menu
.addAction(pCommandManager
->GetAction("general.delete"));
646 QAction
* pAction
= menu
.addAction(QObject::tr("Rename"));
647 QObject::connect(pAction
, &QAction::triggered
, this, [this, index
]()
649 const QModelIndex editIndex
= m_pFilterProxy
->mapFromSource(m_pComponentsList
->model()->index(index
.row(), CComponentsDictionary::Column_Name
, index
.parent()));
650 m_pComponentsList
->edit(editIndex
);
654 const EObjectStructureItemType itemType
= static_cast<EObjectStructureItemType
>(pItem
->GetType());
657 case eObjectItemType_Graph
:
659 case eObjectItemType_StateMachine
:
662 QAction
* pAction
= menu
.addAction(QObject::tr("Add State"));
663 QObject::connect(pAction
, &QAction::triggered
, this, [this, pItem
]()
665 CStateMachineItem
* pStateMachineItem
= static_cast<CStateMachineItem
*>(pItem
);
666 CStateItem
* pCreatedItem
= pStateMachineItem
->CreateState();
669 const QModelIndex index
= m_pFilterProxy
->mapFromSource(m_pDataModel
->GetIndexForItem(*pCreatedItem
));
670 m_pComponentsList
->setCurrentIndex(index
);
671 m_pComponentsList
->edit(index
);
677 case eObjectItemType_State
:
679 PopulateContextMenuForItem(menu
, *static_cast<CStateItem
*>(pItem
));
693 // TODO: Show dictionary menu.
695 //m_pContextMenuContent->SetDictionary(m_pModel->());
698 m_pContextMenu
->ShowAt(menuPos
);
702 void CGraphsWidget::OnAddPressed()
704 const QPoint menuPos
= m_pAddButton
->mapToGlobal(m_pAddButton
->mapFromParent(m_pAddButton
->pos())) + QPoint(0, m_pAddButton
->width());
707 //m_pContextMenuContent->SetDictionary(m_pModel->GetAvailableComponentsDictionary());
709 m_pContextMenu
->ShowAt(menuPos
, QPopupWidget::TopRight
);
712 bool CGraphsWidget::OnDeleteEvent()
714 // TODO: Undo action!
715 const QModelIndex index
= m_pComponentsList
->selectionModel()->currentIndex();
718 CAbstractObjectStructureModelItem
* pItem
= reinterpret_cast<CAbstractObjectStructureModelItem
*>(index
.data(CObjectStructureModel::Role_Pointer
).value
<quintptr
>());
721 CAbstractObjectStructureModelItem
* pParent
= pItem
->GetParentItem();
723 pParent
->RemoveChild(*pItem
);
726 m_pModel
->RemoveItem(*pItem
);
734 void CGraphsWidget::customEvent(QEvent
* pEvent
)
736 if (pEvent
->type() != SandboxEvent::Command
)
738 QWidget::customEvent(pEvent
);
742 CommandEvent
* pCommandEvent
= static_cast<CommandEvent
*>(pEvent
);
743 const string
& command
= pCommandEvent
->GetCommand();
745 if (command
== "general.delete")
747 pEvent
->setAccepted(OnDeleteEvent());
750 QWidget::customEvent(pEvent
);
753 void CGraphsWidget::EditItem(CAbstractObjectStructureModelItem
& item
) const
755 const QModelIndex index
= m_pDataModel
->GetIndexForItem(item
);
756 m_pComponentsList
->setCurrentIndex(m_pFilterProxy
->mapFromSource(index
));
757 m_pComponentsList
->edit(index
);
760 void CGraphsWidget::PopulateContextMenuForItem(QMenu
& menu
, CStateItem
& stateItem
) const
765 QAction
* pAction
= menu
.addAction(QObject::tr("Add State"));
766 QObject::connect(pAction
, &QAction::triggered
, [this, &stateItem
]()
768 CStateItem
* pCreatedItem
= stateItem
.CreateState();
771 EditItem(*pCreatedItem
);
776 QAction
* pAction
= menu
.addAction(QObject::tr("Add Function"));
777 QObject::connect(pAction
, &QAction::triggered
, [this, &stateItem
]()
779 CGraphItem
* pCreatedItem
= stateItem
.CreateGraph(CGraphItem::eGraphType_Function
);
782 EditItem(*pCreatedItem
);
787 QAction
* pAction
= menu
.addAction(QObject::tr("Add Signal"));
788 QObject::connect(pAction
, &QAction::triggered
, [this, &stateItem
]()
790 CRY_ASSERT_MESSAGE(false, "Not yet implemented!");
794 QAction
* pAction
= menu
.addAction(QObject::tr("Add Signals Receiver"));
795 QObject::connect(pAction
, &QAction::triggered
, [this, &stateItem
]()
797 CGraphItem
* pCreatedItem
= stateItem
.CreateGraph(CGraphItem::eGraphType_SignalReceiver
);
800 EditItem(*pCreatedItem
);