/** * RobloxTreeWidget.h * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #pragma once //this will make sure that we can use qt with boost (else moc compiler errors) #ifndef QT_NO_KEYWORDS #define QT_NO_KEYWORDS #endif // Qt Headers #include #include #include // Roblox Headers #include "rbx/signal.h" // Roblox Studio Headers #include "FindDialog.h" #include "PlayersDataManager.h" namespace RBX { class Instance; class DataModel; class Selection; class SelectionChanged; namespace Reflection { class PropertyDescriptor; } } FASTFLAG(StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates) class RobloxTreeWidgetItem; class RobloxTreeWidget; class TreeWidgetDelegate; #define ITEM_IS_DIRTY (1 << 1) #define ITEM_EXPANDED_ONCE (1 << 2) struct DepthCompare { bool operator() (const RobloxTreeWidgetItem* lhs, const RobloxTreeWidgetItem* rhs); }; enum ItemRole { ExpandRole = Qt::UserRole }; enum SearchParams { ParamAny = 0, ParamType = 1, ParamName = 2 }; typedef std::set > InstanceList_SPTR; typedef std::set InstanceList_PTR; typedef std::set TreeWidgetItemList; typedef std::set TreeWidgetItemSortedList; typedef std::pair StringParamPair; typedef std::vector ParamList; typedef std::deque > FilterDeque_SPTR; typedef std::map InstanceMap; class InstanceSelectionHandler { public: virtual void onInstanceSelected(boost::shared_ptr instance) = 0; virtual void onInstanceHovered(boost::shared_ptr instance) = 0; }; //TODO: Consider this to move a new file class InstanceUpdateHandler { public: InstanceUpdateHandler(boost::shared_ptr pInstance); virtual ~InstanceUpdateHandler(); boost::shared_ptr getInstance() { return m_pInstance; } void populateChildren(RobloxTreeWidget* pTreeWidget); virtual RobloxTreeWidget *getTreeWidget() = 0; virtual RobloxTreeWidgetItem *getItemParent() = 0; virtual bool onChildAdded(shared_ptr pChild); virtual bool onChildRemoved(shared_ptr pChild); virtual void onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor); // The update handler needs to use qt slots to marshall to the main thread, // but this object can't inherit QObject because that causes multiple inheritance // and the subclasses can use a templated mixin class because QT moc can't handle // templates. Use this pure virtual stubs to remind subclasses to make Q_SLOTS for // these events. virtual void childAddedSignalHandler(shared_ptr pChild) = 0; virtual void childRemovedSignalHandler(shared_ptr pChild) = 0; virtual void propertyChangedSignalHandler(const RBX::Reflection::PropertyDescriptor* pDescriptor) = 0; void removeFromRemovalList(RobloxTreeWidgetItem *pTreeWidgetItem); RobloxTreeWidgetItem* processChildAdd(shared_ptr pInstance); virtual bool processChildrenAdd(); virtual void processChildRemove(); virtual void processPropertyChange() {} void createWidgetItem(shared_ptr pInstance); int getTreeWidgetDepth() const { return m_treeWidgetDepth; } protected: boost::shared_ptr m_pInstance; rbx::signals::scoped_connection m_cChildAddedConnection; rbx::signals::scoped_connection m_cChildRemovedConnection; rbx::signals::scoped_connection m_cPropertyChangedConnection; InstanceList_SPTR m_PendingItemsToAdd; InstanceList_SPTR m_FilterItemsToAdd; TreeWidgetItemList m_PendingItemsToRemove; int m_treeWidgetDepth; }; class RobloxTreeWidgetItem: public QObject, public QTreeWidgetItem, public InstanceUpdateHandler { Q_OBJECT public: RobloxTreeWidgetItem(int index, RobloxTreeWidget *pTreeWidget, boost::shared_ptr pInstance); RobloxTreeWidgetItem(int index, RobloxTreeWidgetItem *pParentWidgetItem, boost::shared_ptr pInstance); ~RobloxTreeWidgetItem(); RobloxTreeWidget *getTreeWidget(); RobloxTreeWidgetItem *getItemParent(); int itemType() { return m_ItemType; } void requestItemExpand(); void aboutToDelete(RobloxTreeWidget* pTreeWidget); static bool isExplorerItem(const shared_ptr& pInstance); static int getImageIndex(const shared_ptr& pInstance); static int getItemType(const shared_ptr& pInstance); void processPropertyChange() override; bool handleOpen(); int getIndexToInsertAt(shared_ptr pInstance); void setDirty(bool state) { setItemInfo(ITEM_IS_DIRTY, state); } bool isDirty() { return getItemInfo(ITEM_IS_DIRTY); } bool hasFilterChildrenToProcess() { return !m_FilterItemsToAdd.empty(); } bool hasChildrenToProcess(); void setViewState(); void updateFilterItems(); bool isExpandedOnce() { return getItemInfo(ITEM_EXPANDED_ONCE); } bool isItemPendingDeletion() { return m_queuedForDeletion; } bool isInTreeWidget() { return treeWidget() != NULL; } bool removeItemFromTreeWidget(); void takeAllChildren(); void setExpandedOnce(bool state) { setItemInfo(ITEM_EXPANDED_ONCE, state); } void addPlayerSelection(int playerId, int color); void removePlayerSelection(int playerId); protected: void childAddedSignalHandler(shared_ptr pchild) override { QMetaObject::invokeMethod(this, "onChildAdded", Qt::QueuedConnection, Q_ARG(shared_ptr, pchild)); } void childRemovedSignalHandler(shared_ptr pchild) override { QMetaObject::invokeMethod(this, "onChildRemoved", Qt::QueuedConnection, Q_ARG(shared_ptr, pchild)); } void propertyChangedSignalHandler(const RBX::Reflection::PropertyDescriptor* pDescriptor) override { QMetaObject::invokeMethod(this, "onPropertyChanged", Qt::QueuedConnection, Q_ARG(const RBX::Reflection::PropertyDescriptor*, pDescriptor)); } private: void setData(int column, int role, const QVariant & value) override; Q_INVOKABLE bool onChildAdded(shared_ptr pChild) override; Q_INVOKABLE bool onChildRemoved(shared_ptr pChild) override; // override just to mark as Q_SLOT Q_INVOKABLE void onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor) override { if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_queuedForDeletion) return; InstanceUpdateHandler::onPropertyChanged(pDescriptor); } void initData(); bool hasChildren(); void setItemInfo(int bitPosition, bool state) { m_ItemInfo = state ? m_ItemInfo | bitPosition : m_ItemInfo & ~bitPosition; } bool getItemInfo(int bitPosition) { return m_ItemInfo & bitPosition; } int m_ItemInfo; int m_ItemType; bool m_queuedForDeletion; }; class RobloxTreeWidget : public QTreeWidget, public InstanceUpdateHandler { Q_OBJECT public: RobloxTreeWidget(boost::shared_ptr pDataModel); virtual ~RobloxTreeWidget(); boost::shared_ptr dataModel(); RBX::Selection* getSelection(); RobloxTreeWidget* getTreeWidget(); RobloxTreeWidgetItem* getItemParent() { return NULL; } QMutex* treeWidgetMutex() { return &m_treeWidgetMutex; } void addInstance(const RBX::Instance *pInstance, RobloxTreeWidgetItem *pTreeWidgetItem); void eraseInstance(RBX::Instance *pInstance); RobloxTreeWidgetItem* findItemFromInstance(const RBX::Instance *pInstance); void scrollToInstance(RBX::Instance *pInstance); int getIndexToInsertAt(shared_ptr pInstance); void activate(); void deActivate(); void addToUpdateList(RobloxTreeWidgetItem *pTreeWidgetItem); void removeFromUpdateList(RobloxTreeWidgetItem *pTreeWidgetItem); void requestDelete(); void requestViewportUpdate(); void updateSelectionState(RBX::Instance *pInstance); void processChildrenRemoval(); void requestItemDelete(RobloxTreeWidgetItem* item); void removeItemFromRemovalList(RobloxTreeWidgetItem* item) { m_itemsToRemove.erase(item); } bool existsInRemovalList(RobloxTreeWidgetItem* item) { return m_itemsToRemove.find(item) != m_itemsToRemove.end(); } bool isDeletionRequested() { return m_bDeletionRequested; } bool filterInstance(shared_ptr instance); bool filterAncestors(shared_ptr pInstance); bool filterWidgetRecursive(shared_ptr instance, FilterDeque_SPTR& previousFilterStack, const RBX::Time& startTime); QString currentFilter() { return m_currentFilter; } bool isFilterEmpty() { return m_currentFilter.isEmpty() || m_searchParamList.empty(); } void setWidgetFilter(const QString& filterString); bool isFoundItem(shared_ptr instance); bool isAncestorOfFoundItem(shared_ptr instance); void addFilterItemsToUpdateList(); RobloxTreeWidgetItem* lastSelectedItem() { return m_lastSelectedItem; } void eraseLastSelectedItem() { m_lastSelectedItem = NULL; } void addItemToSelection(RBX::Instance* item) { m_selectedItems.insert(item); } void filterWidget(const QString& searchString); void setInstanceSelectionHandler(InstanceSelectionHandler* handler); void itemDeletionRequested(RobloxTreeWidgetItem* pItem); void markLocationToSuppressScrolling(); void setPlayersDataManager(boost::shared_ptr playersDataManager); public Q_SLOTS: void filterWidget(); void requestUpdate(); protected: virtual void focusInEvent(QFocusEvent *event); void childAddedSignalHandler(shared_ptr pchild) override { QMetaObject::invokeMethod(this, "onChildAdded", Qt::QueuedConnection, Q_ARG(shared_ptr, pchild)); } void childRemovedSignalHandler(shared_ptr pchild) override { QMetaObject::invokeMethod(this, "onChildRemoved", Qt::QueuedConnection, Q_ARG(shared_ptr, pchild)); } void propertyChangedSignalHandler(const RBX::Reflection::PropertyDescriptor* pDescriptor) override { QMetaObject::invokeMethod(this, "onPropertyChanged", Qt::QueuedConnection, Q_ARG(void*, (void*)pDescriptor)); } private Q_SLOTS: void onItemSelectionChanged(); void onCloudEditSelectionChanged(int playerId); void onItemCollapsed(QTreeWidgetItem* pWidgetItem); void onItemExpanded(QTreeWidgetItem *pWidgetItem); void onItemDoubleClicked(QTreeWidgetItem *pWidgetItem, int column); void onTreeWidgetUpdate(); void onInsertPart(); void onInsertObject(); void onFilterWidgetUpdate(); void scrollBackToLastMarkedLocation(); bool onChildAdded(shared_ptr pChild) override; bool onChildRemoved(shared_ptr pChild) override; void onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor) override { if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_bDeletionRequested) return; InstanceUpdateHandler::onPropertyChanged(pDescriptor); } private: void onDescendantAdded(shared_ptr pDescendant); void onDescendantRemoved(shared_ptr pDescendant); bool event(QEvent * evt) override; void contextMenuEvent(QContextMenuEvent * evt) override; QMenu* getContextMenu(); void keyPressEvent(QKeyEvent *event) override; void initTreeView(boost::shared_ptr pDataModel); void createTreeRoot(const boost::shared_ptr instance); void onInstanceSelectionChanged(const RBX::SelectionChanged& evt); bool isValidSelection(RBX::Instance *pInstance); void modifyItemsSelectionState(InstanceList_PTR &instancesList, bool select); void mousePressEvent(QMouseEvent * evt) override; void mouseMoveEvent(QMouseEvent * evt) override; void mouseReleaseEvent(QMouseEvent * evt) override; bool canDragSelect(QMouseEvent * evt); void dragEnterEvent(QDragEnterEvent *evt) override; void dragMoveEvent(QDragMoveEvent * evt) override; void dropEvent(QDropEvent * evt) override; bool isSearchTimeUp(const RBX::Time& startTime, int duration); QTreeWidgetItem* getMarkerItem(); boost::shared_ptr m_pDataModel; boost::shared_ptr m_pPlayersDataManager; rbx::signals::scoped_connection m_cInstanceSelectionChanged; rbx::signals::scoped_connection m_cDescendantAddedConnection; rbx::signals::scoped_connection m_cDescendantRemovedConnection; InstanceMap m_InstanceMap; QMutex m_treeWidgetMutex; TreeWidgetItemSortedList m_itemsToUpdate; TreeWidgetItemList m_itemsToRemove; std::set > m_foundItems; std::set > m_ancestorsOfFoundItems; InstanceList_PTR m_selectedItems; InstanceList_PTR m_unSelectedItems; PlayerSelectionMap m_prevCloudEditSelection; shared_ptr m_TreeWidgetDelegate; InstanceSelectionHandler *m_pInstanceSelectionHandler; QRubberBand *m_pRubberBand; QPoint m_rubberBandOrigin; QPoint m_initialOffset; QString m_currentFilter; std::vector m_filterList; ParamList m_searchParamList; bool m_bIgnoreInstanceSelectionChanged; bool m_bIgnoreItemSelectionChanged; bool m_bUpdateRequested; bool m_bViewportUpdateRequested; bool m_bDeletionRequested; bool m_filterRunning; bool m_isActive; RobloxTreeWidgetItem* m_lastSelectedItem; FilterDeque_SPTR m_currentFilterStack; RBX::Time m_filterStartTime; QTreeWidgetItem* m_savedMarkerItem; Q_SIGNALS: void helpTopicChanged(const QString& topic); void focusGained(); void filterSearchFinished(); void finishedProcessing(); void startedProcessing(); }; class RobloxExplorerWidget : public QWidget { Q_OBJECT public: RobloxExplorerWidget(QWidget *pParent = 0); void setCurrentWidget(RobloxTreeWidget* treeWidget); RobloxTreeWidget* getTreeWidget() { return m_robloxTreeWidget; } bool filterHasFocus() { return m_lineEdit->hasFocus(); } Q_SIGNALS: void focusGained(); protected Q_SLOTS: void filterTreeWidget(const QString& text); void onProcessingStarted(); void onProcessingFinished(); void sendWidgetCounter(); private: RobloxTreeWidget* m_robloxTreeWidget; QLineEdit* m_lineEdit; QMovie* m_loadingMovie; QLabel* m_loadingLabel; bool m_sendingCounter; };