mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
2557 lines
71 KiB
C++
2557 lines
71 KiB
C++
/**
|
|
* RobloxTreeWidget.cpp
|
|
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "RobloxTreeWidget.h"
|
|
|
|
// Qt Headers
|
|
#include <QApplication>
|
|
#include <QDragEnterEvent>
|
|
#include <QMimeData>
|
|
#include <QMenu>
|
|
#include <QScrollBar>
|
|
#include <QLineEdit>
|
|
#include <QMetaObject>
|
|
#include <QMovie>
|
|
#include <QStyledItemDelegate>
|
|
|
|
// Roblox Headers
|
|
#include "v8datamodel/DataModel.h"
|
|
#include "v8datamodel/Selection.h"
|
|
#include "v8datamodel/Workspace.h"
|
|
#include "v8datamodel/ChangeHistory.h"
|
|
#include "v8datamodel/GuiObject.h"
|
|
#include "Script/ModuleScript.h"
|
|
#include "script/script.h"
|
|
#include "util/ScopedAssign.h"
|
|
#include "util/BrickColor.h"
|
|
#include "ReflectionMetaData.h"
|
|
#include "v8datamodel/PlayerGui.h"
|
|
|
|
// Roblox Studio Headers
|
|
#include "CommonInsertWidget.h"
|
|
#include "LuaSourceBuffer.h"
|
|
#include "QtUtilities.h"
|
|
#include "RobloxMainWindow.h"
|
|
#include "RobloxContextualHelp.h"
|
|
#include "RobloxCustomWidgets.h"
|
|
#include "RobloxDocManager.h"
|
|
#include "UpdateUIManager.h"
|
|
#include "RobloxIDEDoc.h"
|
|
#include "boost/algorithm/string.hpp"
|
|
#include "util/RobloxGoogleAnalytics.h"
|
|
#include "RobloxSettings.h"
|
|
|
|
const static std::string classSearchString = "classname:";
|
|
const static std::string nameSearchString = "name:";
|
|
|
|
static int nearestWidgetItemIndex(RobloxTreeWidgetItem *pParentTWI, int first, int last, int key);
|
|
static QItemSelection mergeModelIndexes(const QList<QModelIndex> &indexes);
|
|
|
|
FASTINTVARIABLE(StudioTreeWidgetProcessingTime, 100)
|
|
FASTINTVARIABLE(StudioTreeWidgetFilterTime, 30)
|
|
FASTINTVARIABLE(StudioTreeWidgetEventProcessingTime, 10)
|
|
|
|
FASTFLAGVARIABLE(StudioDE8774CrashFixEnabled, false)
|
|
FASTFLAGVARIABLE(StudioPushTreeWidgetUpdatesToMainThread, false)
|
|
FASTFLAGVARIABLE(StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates, false)
|
|
|
|
FASTFLAG(StudioNewWiki)
|
|
FASTFLAG(StudioMimeDataContainsInstancePath)
|
|
FASTFLAG(StudioSeparateActionByActivationMethod)
|
|
FASTFLAG(TeamCreateOptimizeRemoteSelection)
|
|
|
|
LOGGROUP(Explorer)
|
|
|
|
bool DepthCompare::operator() (const RobloxTreeWidgetItem* lhs, const RobloxTreeWidgetItem* rhs)
|
|
{
|
|
if (lhs->getTreeWidgetDepth() == rhs->getTreeWidgetDepth())
|
|
return lhs < rhs;
|
|
|
|
return rhs->getTreeWidgetDepth() < lhs->getTreeWidgetDepth();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// TreeWidgetDelegate
|
|
//--------------------------------------------------------------------------------------------
|
|
class TreeWidgetDelegate: public QStyledItemDelegate
|
|
{
|
|
public:
|
|
TreeWidgetDelegate(QObject* parent)
|
|
: QStyledItemDelegate(parent)
|
|
{
|
|
|
|
}
|
|
|
|
enum Role
|
|
{
|
|
OtherUsersHighlightingRole = Qt::UserRole+1,
|
|
};
|
|
|
|
private:
|
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
{
|
|
return QStyledItemDelegate::sizeHint(option, index);
|
|
}
|
|
|
|
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
{
|
|
// do the normal paint
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
|
|
// now paint other users selection
|
|
// TODO: once we finalize the display, get widget width and add "..."
|
|
QRect newRect(option.rect.topRight(), QSize(15, option.rect.height()));
|
|
PlayersColorCollection playersData = index.data(OtherUsersHighlightingRole).value<PlayersColorCollection >();
|
|
for (PlayersColorCollection::const_iterator iter = playersData.constBegin(); iter != playersData.constEnd(); ++iter)
|
|
{
|
|
painter->fillRect(newRect, QtUtilities::toQColor(RBX::BrickColor(iter.value()).color3()));
|
|
newRect.translate(15, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
InstanceUpdateHandler::InstanceUpdateHandler(boost::shared_ptr<RBX::Instance> pInstance)
|
|
: m_pInstance(pInstance)
|
|
{
|
|
// Attach listeners
|
|
if (FFlag::StudioPushTreeWidgetUpdatesToMainThread)
|
|
{
|
|
m_cChildAddedConnection = m_pInstance->getOrCreateChildAddedSignal()->connect(boost::bind(&InstanceUpdateHandler::childAddedSignalHandler, this, _1));
|
|
m_cChildRemovedConnection = m_pInstance->getOrCreateChildRemovedSignal()->connect(boost::bind(&InstanceUpdateHandler::childRemovedSignalHandler, this, _1));
|
|
m_cPropertyChangedConnection = m_pInstance->propertyChangedSignal.connect(boost::bind(&InstanceUpdateHandler::propertyChangedSignalHandler, this, _1));
|
|
}
|
|
else
|
|
{
|
|
m_cChildAddedConnection = m_pInstance->getOrCreateChildAddedSignal()->connect(boost::bind(&InstanceUpdateHandler::onChildAdded, this, _1));
|
|
m_cChildRemovedConnection = m_pInstance->getOrCreateChildRemovedSignal()->connect(boost::bind(&InstanceUpdateHandler::onChildRemoved, this, _1));
|
|
m_cPropertyChangedConnection = m_pInstance->propertyChangedSignal.connect(boost::bind(&InstanceUpdateHandler::onPropertyChanged, this, _1));
|
|
}
|
|
}
|
|
|
|
InstanceUpdateHandler::~InstanceUpdateHandler()
|
|
{
|
|
m_cChildRemovedConnection.disconnect();
|
|
m_cChildAddedConnection.disconnect();
|
|
m_cPropertyChangedConnection.disconnect();
|
|
|
|
m_PendingItemsToAdd.clear();
|
|
}
|
|
|
|
void InstanceUpdateHandler::populateChildren(RobloxTreeWidget* pTreeWidget)
|
|
{
|
|
shared_ptr<RBX::Instance> inst = getInstance();
|
|
|
|
RobloxTreeWidgetItem* pParent = getItemParent();
|
|
|
|
for (size_t i = 0; i < inst->numChildren(); ++i)
|
|
{
|
|
if (RBX::Instance* pChildInst = inst->getChild(i))
|
|
{
|
|
RobloxTreeWidgetItem* pChildItem = pTreeWidget->findItemFromInstance(pChildInst);
|
|
|
|
if (pChildItem)
|
|
{
|
|
bool currentlySelected = false;
|
|
|
|
if (RBX::Selection* selection = RBX::ServiceProvider::find<RBX::Selection>(getInstance().get()))
|
|
{
|
|
for (RBX::Instances::const_iterator iter = selection->begin(); iter != selection->end(); ++iter)
|
|
{
|
|
if ((*iter).get() == pChildInst)
|
|
{
|
|
currentlySelected = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pParent)
|
|
{
|
|
int index = pParent->getIndexToInsertAt(shared_from(pChildInst));
|
|
pParent->insertChild(index < 0 ? 0 : index, pChildItem);
|
|
}
|
|
else if (pTreeWidget)
|
|
{
|
|
int index = pTreeWidget->getIndexToInsertAt(shared_from(pChildInst));
|
|
pTreeWidget->insertTopLevelItem(index < 0 ? 0 : index, pChildItem);
|
|
}
|
|
|
|
if (currentlySelected)
|
|
pTreeWidget->addItemToSelection(pChildInst);
|
|
|
|
pChildItem->populateChildren(pTreeWidget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool InstanceUpdateHandler::onChildAdded(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
FASTLOG1(FLog::Explorer, "InstanceUpdateHander::onChildAdded, child: %p", pChild.get());
|
|
|
|
if (!pChild || !RobloxTreeWidgetItem::isExplorerItem(pChild))
|
|
return false;
|
|
|
|
FASTLOGS(FLog::Explorer, "Child name: %s", pChild->getName());
|
|
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
if (!pTreeWidget || pTreeWidget->isDeletionRequested())
|
|
return false;
|
|
|
|
RobloxTreeWidgetItem *pTreeWidgetParentItem = pTreeWidget->findItemFromInstance(pChild->getParent());
|
|
|
|
if (pTreeWidgetParentItem)
|
|
pTreeWidgetParentItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
|
|
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
|
|
if (RobloxIDEDoc::getIsCloudEditSession() && RBX::Security::Context::current().identity == RBX::Security::Replicator_)
|
|
pTreeWidget->markLocationToSuppressScrolling();
|
|
|
|
RobloxTreeWidgetItem* pChildItem = pTreeWidget->findItemFromInstance(pChild.get());
|
|
|
|
if (pChildItem && (pTreeWidgetParentItem || pTreeWidget))
|
|
{
|
|
pTreeWidget->removeItemFromRemovalList(pChildItem);
|
|
bool currentlySelected = pChildItem->removeItemFromTreeWidget();
|
|
|
|
if (pTreeWidgetParentItem)
|
|
{
|
|
int index = pTreeWidgetParentItem->getIndexToInsertAt(pChild);
|
|
pTreeWidgetParentItem->insertChild(index < 0 ? 0 : index, pChildItem);
|
|
}
|
|
else if (pTreeWidget)
|
|
{
|
|
int index = pTreeWidget->getIndexToInsertAt(pChild);
|
|
pTreeWidget->insertTopLevelItem(index < 0 ? 0 : index, pChildItem);
|
|
}
|
|
|
|
pChildItem->setExpandedOnce(false);
|
|
|
|
if (currentlySelected)
|
|
pTreeWidget->addItemToSelection(pChild.get());
|
|
|
|
}
|
|
else
|
|
{
|
|
m_PendingItemsToAdd.insert(pChild);
|
|
}
|
|
|
|
if (pTreeWidgetParentItem && pTreeWidgetParentItem->isExpanded())
|
|
{
|
|
pTreeWidgetParentItem->updateFilterItems();
|
|
}
|
|
pTreeWidget->addToUpdateList(pTreeWidgetParentItem);
|
|
return true;
|
|
}
|
|
|
|
bool InstanceUpdateHandler::onChildRemoved(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
FASTLOG1(FLog::Explorer, "InstanceUpdateHander::onChildRemoved, child: %p", pChild.get());
|
|
if (!pChild)
|
|
return false;
|
|
|
|
FASTLOGS(FLog::Explorer, "Child name: %s", pChild->getName());
|
|
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
if (!pTreeWidget || pTreeWidget->isDeletionRequested())
|
|
return false;
|
|
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
m_PendingItemsToAdd.erase(pChild);
|
|
m_FilterItemsToAdd.erase(pChild);
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = pTreeWidget->findItemFromInstance(pChild.get());
|
|
if (pTreeWidgetItem)
|
|
{
|
|
pTreeWidget->requestItemDelete(pTreeWidgetItem);
|
|
|
|
if (pTreeWidgetItem == pTreeWidget->lastSelectedItem())
|
|
pTreeWidget->eraseLastSelectedItem();
|
|
|
|
if (RobloxIDEDoc::getIsCloudEditSession())
|
|
{
|
|
if (RBX::Security::Context::current().identity == RBX::Security::Replicator_)
|
|
pTreeWidget->markLocationToSuppressScrolling();
|
|
pTreeWidget->itemDeletionRequested(pTreeWidgetItem);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void InstanceUpdateHandler::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor)
|
|
{
|
|
bool isNameChanged = (*pDescriptor==RBX::Instance::desc_Name);
|
|
if (isNameChanged)
|
|
{
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
if (pTreeWidget && !pTreeWidget->isDeletionRequested())
|
|
{
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
processPropertyChange();
|
|
}
|
|
}
|
|
else if (*pDescriptor==RBX::Instance::propParent)
|
|
{
|
|
if (RobloxTreeWidget* pTreeWidget = getTreeWidget())
|
|
if (!pTreeWidget->isFilterEmpty() && getInstance()->getParent())
|
|
pTreeWidget->filterWidget();
|
|
}
|
|
}
|
|
|
|
RobloxTreeWidgetItem* InstanceUpdateHandler::processChildAdd(shared_ptr<RBX::Instance> pInstance)
|
|
{
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
RobloxTreeWidgetItem *pWidgetItem = NULL;
|
|
|
|
if (pTreeWidget->findItemFromInstance(pInstance.get()))
|
|
return NULL;
|
|
|
|
if (getItemParent())
|
|
{
|
|
int index = getItemParent()->getIndexToInsertAt(pInstance);
|
|
pWidgetItem = new RobloxTreeWidgetItem((index > 0) ? index : 0, getItemParent(), pInstance);
|
|
}
|
|
else
|
|
{
|
|
int index = pTreeWidget->getIndexToInsertAt(pInstance);
|
|
pWidgetItem = new RobloxTreeWidgetItem((index > 0) ? index : 0, pTreeWidget, pInstance);
|
|
}
|
|
|
|
if (pWidgetItem && pInstance->numChildren() > 0)
|
|
pWidgetItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
|
|
|
|
if (pWidgetItem)
|
|
pWidgetItem->setViewState();
|
|
|
|
pTreeWidget->updateSelectionState(pInstance.get());
|
|
|
|
m_FilterItemsToAdd.erase(pInstance);
|
|
m_PendingItemsToAdd.erase(pInstance);
|
|
|
|
return pWidgetItem;
|
|
}
|
|
|
|
bool InstanceUpdateHandler::processChildrenAdd()
|
|
{
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
RBXASSERT(pTreeWidget);
|
|
if (!pTreeWidget)
|
|
return false;
|
|
|
|
InstanceList_SPTR* instanceList = pTreeWidget->isFilterEmpty() ? &m_PendingItemsToAdd : &m_FilterItemsToAdd;
|
|
|
|
if (instanceList->empty())
|
|
return false;
|
|
|
|
bool itemsAdded = false;
|
|
|
|
//Lock for a few items
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
// Do a few items
|
|
for ( int i = 0 ; i < 10 ; ++i )
|
|
{
|
|
InstanceList_SPTR::iterator iter = instanceList->begin();
|
|
if (iter == instanceList->end())
|
|
return itemsAdded;
|
|
shared_ptr<RBX::Instance> pInstance = *iter;
|
|
|
|
processChildAdd(pInstance);
|
|
|
|
itemsAdded = true;
|
|
}
|
|
return itemsAdded;
|
|
}
|
|
|
|
void InstanceUpdateHandler::removeFromRemovalList(RobloxTreeWidgetItem *pTreeWidgetItem)
|
|
{
|
|
m_PendingItemsToRemove.erase(pTreeWidgetItem);
|
|
|
|
int numChild = pTreeWidgetItem->childCount();
|
|
if (!numChild)
|
|
return;
|
|
|
|
int currentChild = 0;
|
|
RobloxTreeWidgetItem *pCurrentItem = NULL;
|
|
|
|
while (currentChild < numChild)
|
|
{
|
|
pCurrentItem = static_cast<RobloxTreeWidgetItem*>(pTreeWidgetItem->child(currentChild));
|
|
if (pCurrentItem)
|
|
removeFromRemovalList(pCurrentItem);
|
|
++currentChild;
|
|
}
|
|
}
|
|
|
|
void InstanceUpdateHandler::processChildRemove()
|
|
{
|
|
if (!m_PendingItemsToRemove.size())
|
|
return;
|
|
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
|
|
if (FFlag::StudioDE8774CrashFixEnabled)
|
|
{
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
|
|
while (m_PendingItemsToRemove.begin() != m_PendingItemsToRemove.end())
|
|
{
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = *m_PendingItemsToRemove.begin();
|
|
if (!pTreeWidgetItem)
|
|
continue;
|
|
|
|
pTreeWidgetItem->aboutToDelete(pTreeWidget);
|
|
removeFromRemovalList(pTreeWidgetItem);
|
|
|
|
delete pTreeWidgetItem;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::vector<RobloxTreeWidgetItem*> itemsToRemove;
|
|
|
|
// Copy the items to a temp collection
|
|
{
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
for (TreeWidgetItemList::iterator iter = m_PendingItemsToRemove.begin(); iter != m_PendingItemsToRemove.end(); ++iter)
|
|
itemsToRemove.push_back(*iter);
|
|
m_PendingItemsToRemove.clear();
|
|
}
|
|
|
|
// Process them
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = NULL;
|
|
for (std::vector<RobloxTreeWidgetItem*>::iterator iter = itemsToRemove.begin(); iter!=itemsToRemove.end(); ++iter)
|
|
{
|
|
pTreeWidgetItem = *iter;
|
|
if (!pTreeWidgetItem)
|
|
continue;
|
|
pTreeWidgetItem->aboutToDelete(pTreeWidget);
|
|
//if (pTreeWidgetItem->parent())
|
|
// pTreeWidgetItem->parent()->removeChild(pTreeWidgetItem);
|
|
delete pTreeWidgetItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
RobloxTreeWidgetItem::RobloxTreeWidgetItem(int index, RobloxTreeWidget *pTreeWidget, boost::shared_ptr<RBX::Instance> pInstance)
|
|
: InstanceUpdateHandler(pInstance)
|
|
, m_ItemInfo(0)
|
|
, m_queuedForDeletion(false)
|
|
{
|
|
initData();
|
|
|
|
m_treeWidgetDepth = 1;
|
|
|
|
//add it to map in tree view for find
|
|
pTreeWidget->addInstance(m_pInstance.get(), this);
|
|
|
|
//add item to tree view
|
|
pTreeWidget->insertTopLevelItem(index, this);
|
|
}
|
|
|
|
RobloxTreeWidgetItem::RobloxTreeWidgetItem(int index, RobloxTreeWidgetItem *pParentWidgetItem, boost::shared_ptr<RBX::Instance> pInstance)
|
|
: InstanceUpdateHandler(pInstance)
|
|
, m_ItemInfo(0)
|
|
, m_queuedForDeletion(false)
|
|
{
|
|
initData();
|
|
|
|
m_treeWidgetDepth = pParentWidgetItem->getTreeWidgetDepth() + 1;
|
|
|
|
//add it to map in tree view for find (cannot use current item's getTreeWidget)
|
|
pParentWidgetItem->getTreeWidget()->addInstance(m_pInstance.get(), this);
|
|
|
|
//add item as child
|
|
pParentWidgetItem->insertChild(index, this);
|
|
}
|
|
|
|
RobloxTreeWidgetItem::~RobloxTreeWidgetItem()
|
|
{
|
|
RobloxTreeWidget* pTreeWidget = getTreeWidget();
|
|
|
|
if (!pTreeWidget)
|
|
{
|
|
//May not be in treeWidget
|
|
RobloxExplorerWidget* robloxExplorer = static_cast<RobloxExplorerWidget*>(UpdateUIManager::Instance().getExplorerWidget());
|
|
pTreeWidget = robloxExplorer->getTreeWidget();
|
|
}
|
|
|
|
if (pTreeWidget)
|
|
{
|
|
pTreeWidget->removeItemFromRemovalList(this);
|
|
pTreeWidget->eraseInstance(getInstance().get());
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::setData(int column, int role, const QVariant & value)
|
|
{
|
|
//only editing of column 0 i.e. name change is to be handled
|
|
if (column != 0 || role != Qt::EditRole)
|
|
{
|
|
QTreeWidgetItem::setData(column, role, value);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
QString label = value.toString();
|
|
if (!label.isEmpty() && label != m_pInstance->getName().c_str())
|
|
{
|
|
RBX::DataModel::LegacyLock lock(getTreeWidget()->dataModel(), RBX::DataModelJob::Write);
|
|
m_pInstance->setName(label.toStdString());
|
|
|
|
// check to see if rename failed
|
|
if ( m_pInstance->getName() == label.toStdString() )
|
|
{
|
|
//now update the tree widget data
|
|
QTreeWidgetItem::setData(column, role, value);
|
|
|
|
//set waypoint
|
|
RBX::ChangeHistoryService::requestWaypoint("Rename", getTreeWidget()->dataModel().get());
|
|
getTreeWidget()->dataModel()->setDirty(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
catch(std::exception& exp)
|
|
{
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Label edit failed : %s", exp.what());
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::initData()
|
|
{
|
|
setText(0, m_pInstance->getName().c_str());
|
|
int imageIndex = getImageIndex(m_pInstance);
|
|
setIcon(0, QIcon(QtUtilities::getPixmap(":/images/ClassImages.PNG", imageIndex)));
|
|
|
|
m_ItemType = getItemType(m_pInstance);
|
|
|
|
setDirty(true);
|
|
setExpandedOnce(false);
|
|
|
|
setData(1, ExpandRole, QVariant(false));
|
|
|
|
setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled);
|
|
|
|
if (hasChildren())
|
|
setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::hasChildren()
|
|
{
|
|
bool result = false;
|
|
if (m_pInstance->getChildren())
|
|
{
|
|
RBX::Instances childInstances = *m_pInstance->getChildren();
|
|
if (!childInstances.empty())
|
|
{
|
|
for (RBX::Instances::const_iterator iter = childInstances.begin(); iter != childInstances.end(); ++iter)
|
|
{
|
|
if (isExplorerItem(*iter))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::setViewState()
|
|
{
|
|
RobloxTreeWidget* pTreeWidget = getTreeWidget();
|
|
|
|
if (pTreeWidget->isFilterEmpty())
|
|
{
|
|
setExpanded(data(1, ExpandRole).toBool());
|
|
setTextColor(0, Qt::black);
|
|
setHidden(false);
|
|
setDisabled(false);
|
|
}
|
|
else if (pTreeWidget->isFoundItem(getInstance()))
|
|
{
|
|
setExpanded(true);
|
|
setTextColor(0, Qt::black);
|
|
setHidden(false);
|
|
setDisabled(false);
|
|
}
|
|
else if (pTreeWidget->isAncestorOfFoundItem(getInstance()))
|
|
{
|
|
setExpanded(true);
|
|
setTextColor(0, Qt::gray);
|
|
setHidden(false);
|
|
setDisabled(false);
|
|
}
|
|
else
|
|
{
|
|
setHidden(true);
|
|
setDisabled(true);
|
|
}
|
|
}
|
|
|
|
RobloxTreeWidget *RobloxTreeWidgetItem::getTreeWidget()
|
|
{ return dynamic_cast<RobloxTreeWidget *>(treeWidget()); }
|
|
|
|
RobloxTreeWidgetItem* RobloxTreeWidgetItem::getItemParent()
|
|
{ return this; }
|
|
|
|
void RobloxTreeWidgetItem::updateFilterItems()
|
|
{
|
|
m_FilterItemsToAdd.clear();
|
|
|
|
RobloxTreeWidget* pTreeWidget = getTreeWidget();
|
|
RBXASSERT(pTreeWidget);
|
|
if (!pTreeWidget)
|
|
return;
|
|
|
|
if (!pTreeWidget->isFilterEmpty())
|
|
{
|
|
for (std::set<shared_ptr<RBX::Instance> >::const_iterator iter = m_PendingItemsToAdd.begin(); iter != m_PendingItemsToAdd.end(); ++iter)
|
|
if (pTreeWidget->isAncestorOfFoundItem(*iter) || pTreeWidget->isFoundItem(*iter))
|
|
m_FilterItemsToAdd.insert(*iter);
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::requestItemExpand()
|
|
{
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
|
|
RBXASSERT(pTreeWidget);
|
|
if (!pTreeWidget)
|
|
return;
|
|
|
|
if (pTreeWidget->isFilterEmpty())
|
|
setData(1, ExpandRole, QVariant(true));
|
|
|
|
if (m_pInstance->getChildren())
|
|
{
|
|
RBX::Instances childInstances = *m_pInstance->getChildren();
|
|
if (childInstances.empty())
|
|
return;
|
|
|
|
//add child in pending queue
|
|
if (!isExpandedOnce())
|
|
{
|
|
QMutexLocker lock(pTreeWidget->treeWidgetMutex());
|
|
for (RBX::Instances::const_iterator iter = childInstances.begin(); iter != childInstances.end(); ++iter)
|
|
if(!pTreeWidget->findItemFromInstance(iter->get()) &&RobloxTreeWidgetItem::isExplorerItem(*iter))
|
|
m_PendingItemsToAdd.insert(*iter);
|
|
}
|
|
}
|
|
|
|
setExpandedOnce(true);
|
|
|
|
updateFilterItems();
|
|
|
|
pTreeWidget->addToUpdateList(this);
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::takeAllChildren()
|
|
{
|
|
while (childCount() > 0)
|
|
{
|
|
QTreeWidgetItem* firstItem = child(0);
|
|
RobloxTreeWidgetItem* treeItem = dynamic_cast<RobloxTreeWidgetItem*>(firstItem);
|
|
|
|
if (treeItem)
|
|
treeItem->takeAllChildren();
|
|
|
|
this->takeChild(0);
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::addPlayerSelection(int playerId, int color)
|
|
{
|
|
PlayersColorCollection playersData = data(0, TreeWidgetDelegate::OtherUsersHighlightingRole).value<PlayersColorCollection >();
|
|
if (playersData.find(playerId) == playersData.end())
|
|
{
|
|
playersData[playerId] = color;
|
|
setData(0, TreeWidgetDelegate::OtherUsersHighlightingRole, QVariant::fromValue<PlayersColorCollection >(playersData));
|
|
}
|
|
}
|
|
|
|
|
|
void RobloxTreeWidgetItem::removePlayerSelection(int playerId)
|
|
{
|
|
PlayersColorCollection playersData = data(0, TreeWidgetDelegate::OtherUsersHighlightingRole).value<PlayersColorCollection >();
|
|
if (playersData.remove(playerId) > 0)
|
|
setData(0, TreeWidgetDelegate::OtherUsersHighlightingRole, QVariant::fromValue<PlayersColorCollection >(playersData));
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::removeItemFromTreeWidget()
|
|
{
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
if (!pTreeWidget)
|
|
return false;
|
|
|
|
bool currentlySelected = false;
|
|
|
|
if (RBX::Selection* selection = RBX::ServiceProvider::find<RBX::Selection>(getInstance().get()))
|
|
{
|
|
for (RBX::Instances::const_iterator iter = selection->begin(); iter != selection->end(); ++iter)
|
|
{
|
|
if (*iter == getInstance())
|
|
{
|
|
currentlySelected = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentlySelected)
|
|
pTreeWidget->blockSignals(true);
|
|
|
|
|
|
if (RobloxTreeWidgetItem* previousParent = dynamic_cast<RobloxTreeWidgetItem*>(static_cast<QTreeWidgetItem*>(this)->parent()))
|
|
{
|
|
int childIndex = previousParent->indexOfChild(this);
|
|
takeAllChildren();
|
|
previousParent->takeChild(childIndex);
|
|
}
|
|
else
|
|
{
|
|
int childIndex = pTreeWidget->indexOfTopLevelItem(this);
|
|
takeAllChildren();
|
|
pTreeWidget->takeTopLevelItem(childIndex);
|
|
}
|
|
|
|
if (currentlySelected)
|
|
pTreeWidget->blockSignals(false);
|
|
|
|
return currentlySelected;
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::aboutToDelete(RobloxTreeWidget* pTreeWidget)
|
|
{
|
|
m_queuedForDeletion = true;
|
|
|
|
m_cChildAddedConnection.disconnect();
|
|
m_cChildRemovedConnection.disconnect();
|
|
m_cPropertyChangedConnection.disconnect();
|
|
|
|
pTreeWidget->eraseInstance(getInstance().get());
|
|
pTreeWidget->removeItemFromRemovalList(this);
|
|
|
|
pTreeWidget->removeFromUpdateList(this);
|
|
|
|
int numChild = childCount();
|
|
if (!numChild)
|
|
{
|
|
removeItemFromTreeWidget();
|
|
return;
|
|
}
|
|
|
|
int currentChild = 0;
|
|
RobloxTreeWidgetItem *pCurrentItem = NULL;
|
|
|
|
while (currentChild < numChild)
|
|
{
|
|
pCurrentItem = static_cast<RobloxTreeWidgetItem*>(child(currentChild));
|
|
if (pCurrentItem)
|
|
{
|
|
pCurrentItem->aboutToDelete(pTreeWidget);
|
|
pCurrentItem->deleteLater();
|
|
}
|
|
++currentChild;
|
|
}
|
|
|
|
if (!FFlag::StudioDE8774CrashFixEnabled)
|
|
pTreeWidget->removeFromUpdateList(this);
|
|
|
|
removeItemFromTreeWidget();
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::onChildAdded(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_queuedForDeletion) return false;
|
|
|
|
FASTLOG1(FLog::Explorer, "RobloxTreeWidgetItem::onChildAdded, child: %p", pChild.get());
|
|
|
|
RobloxTreeWidget *pTreeWidget = getTreeWidget();
|
|
if (!pTreeWidget)
|
|
return false;
|
|
|
|
FASTLOGS(FLog::Explorer, "Child name: %s", pChild->getName());
|
|
|
|
//if not expanded once then just set dirty flag
|
|
if (!isExpandedOnce())
|
|
{
|
|
if (RobloxTreeWidgetItem::isExplorerItem(pChild))
|
|
{
|
|
setDirty(true);
|
|
setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
|
|
pTreeWidget->requestViewportUpdate();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!InstanceUpdateHandler::onChildAdded(pChild))
|
|
return false;
|
|
|
|
if (isExpandedOnce())
|
|
pTreeWidget->addToUpdateList(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::onChildRemoved(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_queuedForDeletion) return false;
|
|
|
|
FASTLOG1(FLog::Explorer, "RobloxTreeWidgetItem::onChildRemoved, child: %p", pChild.get());
|
|
//if child item has been added before then let it get removed!
|
|
if (!InstanceUpdateHandler::onChildRemoved(pChild))
|
|
return false;
|
|
|
|
FASTLOGS(FLog::Explorer, "Child name: %s", pChild->getName());
|
|
|
|
RobloxTreeWidget* treeWidget = getTreeWidget();
|
|
|
|
if (!treeWidget)
|
|
return false;
|
|
|
|
if (!hasChildren())
|
|
{
|
|
setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless);
|
|
getTreeWidget()->requestViewportUpdate();
|
|
}
|
|
|
|
//request for an update
|
|
if (isExpandedOnce())
|
|
getTreeWidget()->addToUpdateList(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::hasChildrenToProcess()
|
|
{
|
|
if (RobloxTreeWidget* pWidget = getTreeWidget())
|
|
if (!pWidget->isFilterEmpty())
|
|
return !m_FilterItemsToAdd.empty();
|
|
|
|
return !m_PendingItemsToAdd.empty();
|
|
}
|
|
|
|
void RobloxTreeWidgetItem::processPropertyChange()
|
|
{
|
|
setText(0, m_pInstance->getName().c_str());
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::handleOpen()
|
|
{
|
|
if(!m_pInstance)
|
|
return false;
|
|
|
|
// Open script document
|
|
return RobloxDocManager::Instance().openDoc(LuaSourceBuffer::fromInstance(m_pInstance));
|
|
}
|
|
|
|
int RobloxTreeWidgetItem::getIndexToInsertAt(shared_ptr<RBX::Instance> pInstance)
|
|
{
|
|
int numChild = childCount();
|
|
if (!numChild)
|
|
return -1;
|
|
|
|
int itemTypeToAdd = RobloxTreeWidgetItem::getItemType(pInstance);
|
|
int nearestIndex = nearestWidgetItemIndex(this, 0, childCount()-1, itemTypeToAdd);
|
|
|
|
RobloxTreeWidgetItem *pCurrentItem = static_cast<RobloxTreeWidgetItem*>(child(nearestIndex));
|
|
if (!pCurrentItem || (itemTypeToAdd != pCurrentItem->itemType()))
|
|
return nearestIndex;
|
|
|
|
//if the type is same then we need to insert in the order of item's name
|
|
if ((pInstance->getName() < pCurrentItem->getInstance()->getName()))
|
|
{
|
|
while (nearestIndex >= 0)
|
|
{
|
|
pCurrentItem = static_cast<RobloxTreeWidgetItem*>(child(nearestIndex));
|
|
if (!pCurrentItem)
|
|
break;
|
|
|
|
if((itemTypeToAdd != pCurrentItem->itemType()) || (pInstance->getName() > pCurrentItem->getInstance()->getName()))
|
|
{
|
|
nearestIndex++;
|
|
break;
|
|
}
|
|
|
|
--nearestIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (nearestIndex < childCount())
|
|
{
|
|
pCurrentItem = static_cast<RobloxTreeWidgetItem*>(child(nearestIndex));
|
|
if (!pCurrentItem || (itemTypeToAdd != pCurrentItem->itemType()) || (pInstance->getName() < pCurrentItem->getInstance()->getName()))
|
|
break;
|
|
++nearestIndex;
|
|
}
|
|
}
|
|
|
|
return nearestIndex;
|
|
}
|
|
|
|
bool RobloxTreeWidgetItem::isExplorerItem(const shared_ptr<RBX::Instance>& pInstance)
|
|
{
|
|
// Assume everything that isn't a direct child of data model should be shown in explorer
|
|
if (pInstance->getParent() != RBX::DataModel::get(pInstance.get()))
|
|
{
|
|
RBX::CoreGuiService* cgs = RBX::ServiceProvider::find<RBX::CoreGuiService>(pInstance.get());
|
|
if (cgs && pInstance->isDescendantOf(cgs))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// For everything else, check the class metadata
|
|
boost::shared_ptr<const RBX::Reflection::Metadata::Reflection> pReflection = RBX::Reflection::Metadata::Reflection::singleton();
|
|
RBX::Reflection::Metadata::Class* pClassData = pReflection ? pReflection->get(pInstance->getDescriptor(), true) : NULL;
|
|
if(pClassData && pClassData->isExplorerItem())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int RobloxTreeWidgetItem::getImageIndex(const shared_ptr<RBX::Instance>& pInstance)
|
|
{
|
|
boost::shared_ptr<const RBX::Reflection::Metadata::Reflection> pReflection = RBX::Reflection::Metadata::Reflection::singleton();
|
|
RBX::Reflection::Metadata::Class* pClassData = pReflection ? pReflection->get(pInstance->getDescriptor(), true) : NULL;
|
|
if(pClassData)
|
|
return pClassData->getExplorerImageIndex();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int RobloxTreeWidgetItem::getItemType(const shared_ptr<RBX::Instance>& pInstance)
|
|
{
|
|
boost::shared_ptr<const RBX::Reflection::Metadata::Reflection> pReflection = RBX::Reflection::Metadata::Reflection::singleton();
|
|
RBX::Reflection::Metadata::Class* pClassData = pReflection->get(pInstance->getDescriptor(), true);
|
|
return pClassData ? pClassData->getExplorerOrder() : 0;
|
|
}
|
|
|
|
RobloxTreeWidget::RobloxTreeWidget(boost::shared_ptr<RBX::DataModel> pDataModel)
|
|
: InstanceUpdateHandler(pDataModel)
|
|
, m_pDataModel(pDataModel)
|
|
, m_treeWidgetMutex(QMutex::Recursive)
|
|
, m_pInstanceSelectionHandler(NULL)
|
|
, m_pRubberBand(NULL)
|
|
, m_bIgnoreInstanceSelectionChanged(false)
|
|
, m_bIgnoreItemSelectionChanged(false)
|
|
, m_bUpdateRequested(false)
|
|
, m_bViewportUpdateRequested(false)
|
|
, m_bDeletionRequested(false)
|
|
, m_currentFilter("")
|
|
, m_filterRunning(false)
|
|
, m_lastSelectedItem(NULL)
|
|
, m_isActive(false)
|
|
, m_savedMarkerItem(NULL)
|
|
{
|
|
setHeaderHidden(true);
|
|
setUniformRowHeights(true);
|
|
|
|
setDragEnabled(true);
|
|
setDragDropMode(QAbstractItemView::DragDrop);
|
|
setDropIndicatorShown(true);
|
|
setAutoScroll(true);
|
|
setAutoScrollMargin(32);
|
|
|
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
setSelectionBehavior(QAbstractItemView::SelectItems);
|
|
|
|
header()->setStretchLastSection(false);
|
|
header()->setResizeMode(0,QHeaderView::ResizeToContents);
|
|
|
|
// Set edit trigger only for SelectionClicked and EditKeyPressed
|
|
setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed);
|
|
|
|
if (!UpdateUIManager::Instance().getMainWindow().isRibbonStyle())
|
|
{
|
|
setStyleSheet(STRINGIFY(
|
|
QTreeView
|
|
{
|
|
show-decoration-selected: 1;
|
|
selection-color: white;
|
|
}
|
|
|
|
QTreeView::item
|
|
{
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
QTreeView::item:hover
|
|
{
|
|
border: 1px solid #BFCDE4;
|
|
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E7EFFD, stop: 1 #CBDAF1);
|
|
}
|
|
|
|
QTreeView::item:selected
|
|
{
|
|
border: 1px solid #567DBC;
|
|
}
|
|
|
|
QTreeView::item:selected:active
|
|
{
|
|
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6EA1F1, stop: 1 #567DBC);
|
|
}
|
|
|
|
QTreeView::item:selected:!active
|
|
{
|
|
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6B9BE8, stop: 1 #577FBF);
|
|
}
|
|
) );
|
|
}
|
|
|
|
initTreeView(pDataModel);
|
|
|
|
connect(this, SIGNAL(helpTopicChanged(const QString&)),
|
|
&RobloxContextualHelpService::singleton(), SLOT(onHelpTopicChanged(const QString&)));
|
|
|
|
m_cDescendantAddedConnection = pDataModel->getWorkspace()->getOrCreateDescendantAddedSignal()->connect(boost::bind(&RobloxTreeWidget::onDescendantAdded, this, _1));
|
|
}
|
|
|
|
RobloxTreeWidget::~RobloxTreeWidget()
|
|
{
|
|
deActivate();
|
|
|
|
m_InstanceMap.clear();
|
|
m_itemsToUpdate.clear();
|
|
m_selectedItems.clear();
|
|
m_unSelectedItems.clear();
|
|
}
|
|
|
|
void RobloxTreeWidget::addInstance(const RBX::Instance *pInstance, RobloxTreeWidgetItem *pTreeWidgetItem)
|
|
{
|
|
m_InstanceMap[pInstance] = pTreeWidgetItem;
|
|
}
|
|
|
|
void RobloxTreeWidget::initTreeView(boost::shared_ptr<RBX::DataModel> pDataModel)
|
|
{
|
|
if (!pDataModel->getChildren())
|
|
return;
|
|
|
|
RBX::Instances instances;
|
|
instances = *pDataModel->getChildren();
|
|
|
|
std::for_each(instances.begin(), instances.end(), boost::bind(&RobloxTreeWidget::createTreeRoot, this, _1));
|
|
}
|
|
|
|
void RobloxTreeWidget::focusInEvent(QFocusEvent *event)
|
|
{
|
|
eraseLastSelectedItem();
|
|
QTreeWidget::focusInEvent(event);
|
|
Q_EMIT focusGained();
|
|
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
}
|
|
|
|
void RobloxTreeWidget::createTreeRoot(const boost::shared_ptr<RBX::Instance> pInstance)
|
|
{
|
|
if (RobloxTreeWidgetItem::isExplorerItem(pInstance))
|
|
{
|
|
int index = getIndexToInsertAt(pInstance);
|
|
RobloxTreeWidgetItem* item = new RobloxTreeWidgetItem((index > 0) ? index : 0, this, pInstance);
|
|
}
|
|
}
|
|
|
|
RobloxTreeWidget* RobloxTreeWidget::getTreeWidget()
|
|
{ return this; }
|
|
|
|
RBX::Selection* RobloxTreeWidget::getSelection()
|
|
{ return RBX::ServiceProvider::create<RBX::Selection>(m_pDataModel.get()); }
|
|
|
|
boost::shared_ptr<RBX::DataModel> RobloxTreeWidget::dataModel()
|
|
{ return m_pDataModel; }
|
|
|
|
void RobloxTreeWidget::eraseInstance(RBX::Instance *pInstance)
|
|
{
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
|
|
m_selectedItems.erase(pInstance);
|
|
m_unSelectedItems.erase(pInstance);
|
|
|
|
InstanceMap::iterator iter = m_InstanceMap.find(pInstance);
|
|
if (iter != m_InstanceMap.end())
|
|
{
|
|
if (m_lastSelectedItem == iter->second)
|
|
eraseLastSelectedItem();
|
|
|
|
removeFromUpdateList(iter->second);
|
|
m_InstanceMap.erase(iter);
|
|
}
|
|
}
|
|
|
|
RobloxTreeWidgetItem* RobloxTreeWidget::findItemFromInstance(const RBX::Instance *pInstance)
|
|
{
|
|
InstanceMap::iterator iter = m_InstanceMap.find(pInstance);
|
|
if (iter != m_InstanceMap.end())
|
|
return iter->second;
|
|
return NULL;
|
|
}
|
|
|
|
void RobloxTreeWidget::scrollToInstance(RBX::Instance *pInstance)
|
|
{
|
|
if(!pInstance)
|
|
return;
|
|
|
|
if(isValidSelection(pInstance))
|
|
{
|
|
RobloxTreeWidgetItem* item = findItemFromInstance(pInstance);
|
|
if(item)
|
|
scrollToItem(item);
|
|
else if (RobloxTreeWidgetItem* parentItem = findItemFromInstance(pInstance->getParent()))
|
|
parentItem->setExpanded(true);
|
|
|
|
}
|
|
}
|
|
|
|
int RobloxTreeWidget::getIndexToInsertAt(shared_ptr<RBX::Instance> pInstance)
|
|
{
|
|
int numChild = topLevelItemCount();
|
|
if (!numChild)
|
|
return -1;
|
|
|
|
int itemTypeToAdd = RobloxTreeWidgetItem::getItemType(pInstance), currentChild = 0;
|
|
RobloxTreeWidgetItem *pCurrentItem = NULL;
|
|
|
|
while (currentChild < numChild)
|
|
{
|
|
pCurrentItem = static_cast<RobloxTreeWidgetItem*>(topLevelItem(currentChild));
|
|
if (itemTypeToAdd < pCurrentItem->itemType())
|
|
break;
|
|
|
|
if (itemTypeToAdd == pCurrentItem->itemType())
|
|
{
|
|
if (pInstance->getName() < pCurrentItem->getInstance()->getName())
|
|
break;
|
|
}
|
|
|
|
++currentChild;
|
|
}
|
|
|
|
return currentChild;
|
|
}
|
|
|
|
void RobloxTreeWidget::activate()
|
|
{
|
|
if (m_isActive)
|
|
return;
|
|
m_isActive = true;
|
|
|
|
connect((QTreeWidget *)this, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged()));
|
|
connect((QTreeWidget *)this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(onItemExpanded(QTreeWidgetItem*)));
|
|
connect((QTreeWidget *)this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(onItemCollapsed(QTreeWidgetItem*)));
|
|
connect((QTreeWidget *)this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
|
|
|
|
if (m_pDataModel)
|
|
{
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
|
|
RBX::Selection* pSelection = m_pDataModel->create<RBX::Selection>();
|
|
if (pSelection)
|
|
m_cInstanceSelectionChanged = pSelection->selectionChanged.connect(boost::bind(&RobloxTreeWidget::onInstanceSelectionChanged, this, _1));
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidget::deActivate()
|
|
{
|
|
if (!m_isActive)
|
|
m_isActive = false;
|
|
|
|
disconnect((QTreeWidget *)this, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged()));
|
|
m_cInstanceSelectionChanged.disconnect();
|
|
|
|
disconnect((QTreeWidget *)this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(onItemExpanded(QTreeWidgetItem*)));
|
|
disconnect((QTreeWidget *)this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(onItemCollapsed(QTreeWidgetItem*)));
|
|
disconnect((QTreeWidget *)this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
|
|
}
|
|
|
|
bool RobloxTreeWidget::onChildAdded(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_bDeletionRequested) return false;
|
|
|
|
if (!InstanceUpdateHandler::onChildAdded(pChild))
|
|
return false;
|
|
requestUpdate();
|
|
return true;
|
|
}
|
|
|
|
bool RobloxTreeWidget::onChildRemoved(shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
if (FFlag::StudioTreeWidgetCheckDeletingFlagWhenDoingUpdates && m_bDeletionRequested) return false;
|
|
|
|
if (!InstanceUpdateHandler::onChildRemoved(pChild))
|
|
return false;
|
|
requestUpdate();
|
|
return true;
|
|
}
|
|
|
|
void RobloxTreeWidget::onDescendantAdded(shared_ptr<RBX::Instance> pDescendant)
|
|
{
|
|
if (!isFilterEmpty())
|
|
{
|
|
if (filterInstance(pDescendant) && filterAncestors(pDescendant))
|
|
requestUpdate();
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidget::contextMenuEvent(QContextMenuEvent * evt)
|
|
{
|
|
QMenu menu;
|
|
|
|
QList<QAction*> commonActions;
|
|
UpdateUIManager::Instance().commonContextMenuActions(commonActions);
|
|
|
|
QAction *pAction = NULL;
|
|
for (int ii=0; ii<commonActions.size(); ++ii)
|
|
{
|
|
pAction = commonActions.at(ii);
|
|
if (pAction)
|
|
{
|
|
// hack to add sub menu for inserting basic objects
|
|
if ( pAction == UpdateUIManager::Instance().getMainWindow().insertIntoFileAction )
|
|
{
|
|
RBX::Instance* parent = NULL;
|
|
if ( currentItem() )
|
|
{
|
|
RobloxTreeWidgetItem* item = static_cast<RobloxTreeWidgetItem*>(currentItem());
|
|
parent = item->getInstance().get();
|
|
}
|
|
|
|
if (!parent)
|
|
parent = m_pDataModel->getWorkspace();
|
|
|
|
menu.addAction(
|
|
QIcon(QtUtilities::getPixmap(":/images/ClassImages.PNG",1)), // part icon
|
|
"Insert Part",
|
|
this,
|
|
SLOT(onInsertPart()) );
|
|
menu.addMenu(InsertObjectWidget::createMenu(parent,this,SLOT(onInsertObject())));
|
|
}
|
|
menu.addAction(pAction);
|
|
}
|
|
else
|
|
menu.addSeparator();
|
|
}
|
|
|
|
menu.addSeparator();
|
|
menu.addAction(UpdateUIManager::Instance().getMainWindow().launchHelpForSelectionAction);
|
|
|
|
//TODO: Add other tree view actions
|
|
connect(&menu, SIGNAL(aboutToShow()), &UpdateUIManager::Instance(), SLOT(onMenuShow()));
|
|
connect(&menu, SIGNAL(aboutToHide()), &UpdateUIManager::Instance(), SLOT(onMenuHide()));
|
|
|
|
menu.exec(evt->globalPos());
|
|
}
|
|
|
|
/**
|
|
* Callback for user clicking on insert part in context menu menu.
|
|
*/
|
|
void RobloxTreeWidget::onInsertPart()
|
|
{
|
|
InsertObjectWidget::InsertObject("Part",m_pDataModel,InsertObjectWidget::InsertMode_TreeWidget);
|
|
if (FFlag::StudioSeparateActionByActivationMethod)
|
|
UpdateUIManager::Instance().getMainWindow().sendActionCounterEvent("insertPartMainContext");
|
|
}
|
|
|
|
/**
|
|
* Callback for user clicking on insert basic object in context menu sub menu.
|
|
*/
|
|
void RobloxTreeWidget::onInsertObject()
|
|
{
|
|
QAction* action = static_cast<QAction*>(sender());
|
|
QString className = action->text();
|
|
InsertObjectWidget::InsertObject(className,m_pDataModel,InsertObjectWidget::InsertMode_TreeWidget);
|
|
if (FFlag::StudioSeparateActionByActivationMethod)
|
|
UpdateUIManager::Instance().getMainWindow().sendActionCounterEvent(QString("insert%1Context").arg(className));
|
|
}
|
|
|
|
void RobloxTreeWidget::keyPressEvent(QKeyEvent *evt)
|
|
{
|
|
if ( (state() != EditingState) && ((evt->key() == Qt::Key_Enter) || (evt->key() == Qt::Key_Return)) )
|
|
{
|
|
RobloxTreeWidgetItem* pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(currentItem());
|
|
//handle appropriate document open
|
|
if (pTreeWidgetItem && pTreeWidgetItem->handleOpen())
|
|
{
|
|
//accept event
|
|
evt->accept();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (QKeySequence(evt->key() | evt->modifiers()) == UpdateUIManager::Instance().getMainWindow().zoomExtentsAction->shortcut())
|
|
{
|
|
UpdateUIManager::Instance().getMainWindow().zoomExtentsAction->activate(QAction::Trigger);
|
|
evt->accept();
|
|
return;
|
|
}
|
|
|
|
QTreeWidget::keyPressEvent(evt);
|
|
}
|
|
|
|
void RobloxTreeWidget::processChildrenRemoval()
|
|
{
|
|
TreeWidgetItemList::const_iterator iter = m_itemsToRemove.begin();
|
|
while (iter != m_itemsToRemove.end())
|
|
{
|
|
if (RobloxTreeWidgetItem* pTreeWidgetItem = *iter)
|
|
{
|
|
pTreeWidgetItem->aboutToDelete(this);
|
|
pTreeWidgetItem->deleteLater();
|
|
}
|
|
else
|
|
{
|
|
m_itemsToRemove.erase(iter);
|
|
}
|
|
|
|
iter = m_itemsToRemove.begin();
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidget::requestItemDelete(RobloxTreeWidgetItem* item)
|
|
{
|
|
for (int i = 0; i < item->childCount(); ++i)
|
|
if (RobloxTreeWidgetItem* childItem = dynamic_cast<RobloxTreeWidgetItem*>((item)->child(i)))
|
|
requestItemDelete(childItem);
|
|
|
|
m_itemsToRemove.insert(item);
|
|
removeFromUpdateList(item);
|
|
}
|
|
|
|
void RobloxTreeWidget::onInstanceSelectionChanged(const RBX::SelectionChanged& evt)
|
|
{
|
|
if (evt.addedItem != NULL)
|
|
if(RBX::Instance *pInstance = evt.addedItem.get())
|
|
if(RBX::GuiObject* guiObject = dynamic_cast<RBX::GuiObject*>(pInstance))
|
|
guiObject->setSelectionBox(true);
|
|
|
|
if (evt.removedItem != NULL)
|
|
if(RBX::Instance *pInstance = evt.removedItem.get())
|
|
if(RBX::GuiObject* guiObject = dynamic_cast<RBX::GuiObject*>(pInstance))
|
|
guiObject->setSelectionBox(false);
|
|
|
|
|
|
if (m_bIgnoreInstanceSelectionChanged)
|
|
return;
|
|
|
|
RBX::ScopedAssign<bool> ignoreSelectionEvent(m_bIgnoreItemSelectionChanged, true);
|
|
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
bool isModificationRequired = false;
|
|
|
|
if (evt.addedItem != NULL)
|
|
{
|
|
RBX::Instance* pInstance = evt.addedItem.get();
|
|
if (isValidSelection(pInstance))
|
|
{
|
|
m_unSelectedItems.erase(pInstance);
|
|
m_selectedItems.insert(pInstance);
|
|
|
|
isModificationRequired = true;
|
|
}
|
|
}
|
|
|
|
if (evt.removedItem != NULL)
|
|
{
|
|
RBX::Instance* pInstance = evt.removedItem.get();
|
|
|
|
m_selectedItems.erase(pInstance);
|
|
m_unSelectedItems.insert(pInstance);
|
|
isModificationRequired = true;
|
|
}
|
|
|
|
if (isModificationRequired)
|
|
requestUpdate();
|
|
}
|
|
|
|
void RobloxTreeWidget::onCloudEditSelectionChanged(int playerId)
|
|
{
|
|
RBXASSERT(QThread::currentThread() == qApp->thread());
|
|
if (!m_pPlayersDataManager)
|
|
{
|
|
RBXASSERT(false);
|
|
return;
|
|
}
|
|
|
|
bool enabled = updatesEnabled();
|
|
setUpdatesEnabled(false);
|
|
|
|
if (FFlag::TeamCreateOptimizeRemoteSelection)
|
|
{
|
|
shared_ptr<const RBX::Instances> oldSelection;
|
|
PlayerSelectionMap::iterator iter = m_prevCloudEditSelection.find(playerId);
|
|
if (iter != m_prevCloudEditSelection.end())
|
|
oldSelection = iter->second;
|
|
else
|
|
oldSelection.reset(new RBX::Instances());
|
|
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
|
|
RBX::Instances::const_iterator findIter;
|
|
shared_ptr<const RBX::Instances> currentSelection = m_pPlayersDataManager->getLastKnownSelection(playerId);
|
|
|
|
bool blockSignals = model()->blockSignals(true);
|
|
|
|
for (auto& selectedItem : *currentSelection)
|
|
{
|
|
findIter = std::find(oldSelection->begin(), oldSelection->end(), selectedItem);
|
|
if (findIter == oldSelection->end())
|
|
{
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(selectedItem.get()))
|
|
item->addPlayerSelection(playerId, m_pPlayersDataManager->getPlayerColor(playerId));
|
|
}
|
|
}
|
|
|
|
for (auto& oldSelectedItem : *oldSelection)
|
|
{
|
|
findIter = std::find(currentSelection->begin(), currentSelection->end(), oldSelectedItem);
|
|
if (findIter == currentSelection->end())
|
|
{
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(oldSelectedItem.get()))
|
|
item->removePlayerSelection(playerId);
|
|
}
|
|
}
|
|
|
|
model()->blockSignals(blockSignals);
|
|
|
|
// TODO: check if we need to break it into smaller chunks to clear main thread asap?
|
|
|
|
// save current selection so we can update unselected instances in next change
|
|
m_prevCloudEditSelection[playerId] = currentSelection;
|
|
setUpdatesEnabled(enabled);
|
|
}
|
|
else
|
|
{
|
|
RBX::Instances itemsToUnselect;
|
|
PlayerSelectionMap::iterator iter = m_prevCloudEditSelection.find(playerId);
|
|
if (iter != m_prevCloudEditSelection.end())
|
|
itemsToUnselect = *(iter->second);
|
|
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
|
|
|
|
// TODO: check if we need to break it into smaller chunks to clear main thread asap?
|
|
RBX::Instances::iterator findIter;
|
|
shared_ptr<const RBX::Instances> currentSelection = m_pPlayersDataManager->getLastKnownSelection(playerId);
|
|
for (auto& selectedItem : *currentSelection)
|
|
{
|
|
findIter = std::find(itemsToUnselect.begin(), itemsToUnselect.end(), selectedItem);
|
|
if (findIter == itemsToUnselect.end())
|
|
{
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(selectedItem.get()))
|
|
item->addPlayerSelection(playerId, m_pPlayersDataManager->getPlayerColor(playerId));
|
|
}
|
|
else
|
|
{
|
|
itemsToUnselect.erase(findIter);
|
|
}
|
|
}
|
|
|
|
for (auto& unselectedItem : itemsToUnselect)
|
|
{
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(unselectedItem.get()))
|
|
item->removePlayerSelection(playerId);
|
|
}
|
|
|
|
// save current selection so we can update unselected instances in next change
|
|
m_prevCloudEditSelection[playerId] = currentSelection;
|
|
setUpdatesEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
bool RobloxTreeWidget::isSearchTimeUp(const RBX::Time& startTime, int duration)
|
|
{
|
|
return (RBX::Time::now<RBX::Time::Fast>() - startTime).msec() > duration;
|
|
}
|
|
|
|
void RobloxTreeWidget::addFilterItemsToUpdateList()
|
|
{
|
|
for (std::set<shared_ptr<RBX::Instance> >::const_iterator iter = m_ancestorsOfFoundItems.begin(); iter != m_ancestorsOfFoundItems.end(); ++iter)
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(iter->get()))
|
|
addToUpdateList(item);
|
|
|
|
for (std::set<shared_ptr<RBX::Instance> >::const_iterator iter = m_foundItems.begin(); iter != m_foundItems.end(); ++iter)
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(iter->get()))
|
|
addToUpdateList(item);
|
|
}
|
|
|
|
void RobloxTreeWidget::onFilterWidgetUpdate()
|
|
{
|
|
if (isHidden())
|
|
return;
|
|
|
|
Q_EMIT startedProcessing();
|
|
|
|
FilterDeque_SPTR previousFilterStack = FilterDeque_SPTR(m_currentFilterStack.begin(), m_currentFilterStack.end());
|
|
m_currentFilterStack.clear();
|
|
|
|
{
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Read);
|
|
filterWidgetRecursive(dataModel(), previousFilterStack, RBX::Time::now<RBX::Time::Fast>());
|
|
}
|
|
|
|
if (!m_currentFilterStack.empty())
|
|
{
|
|
QTimer::singleShot(FInt::StudioTreeWidgetEventProcessingTime, this, SLOT(onFilterWidgetUpdate()));
|
|
}
|
|
else
|
|
{
|
|
m_filterRunning = false;
|
|
addFilterItemsToUpdateList();
|
|
requestUpdate();
|
|
Q_EMIT filterSearchFinished();
|
|
}
|
|
}
|
|
|
|
bool RobloxTreeWidget::filterAncestors(shared_ptr<RBX::Instance> instance)
|
|
{
|
|
if (!instance->getParent())
|
|
return true;
|
|
|
|
if (filterAncestors(shared_from(instance->getParent())))
|
|
{
|
|
if (!RobloxTreeWidgetItem::isExplorerItem(instance))
|
|
return false;
|
|
|
|
if (filterInstance(instance))
|
|
m_foundItems.insert(instance);
|
|
else
|
|
m_ancestorsOfFoundItems.insert(instance);
|
|
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(instance.get()))
|
|
addToUpdateList(item);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RobloxTreeWidget::filterWidget(const QString& searchString)
|
|
{
|
|
setWidgetFilter(searchString);
|
|
filterWidget();
|
|
}
|
|
|
|
void RobloxTreeWidget::filterWidget()
|
|
{
|
|
m_currentFilterStack.clear();
|
|
m_foundItems.clear();
|
|
m_ancestorsOfFoundItems.clear();
|
|
m_itemsToUpdate.clear();
|
|
|
|
for (int i = 0; i < invisibleRootItem()->childCount(); ++i)
|
|
{
|
|
invisibleRootItem()->child(i)->setDisabled(true);
|
|
invisibleRootItem()->child(i)->setHidden(true);
|
|
}
|
|
|
|
if (!m_filterRunning)
|
|
{
|
|
m_filterRunning = true;
|
|
QTimer::singleShot(0, this, SLOT(onFilterWidgetUpdate()));
|
|
}
|
|
}
|
|
|
|
bool RobloxTreeWidget::filterInstance(shared_ptr<RBX::Instance> instance)
|
|
{
|
|
for (ParamList::const_iterator iter = m_searchParamList.begin(); iter != m_searchParamList.end(); ++iter)
|
|
{
|
|
switch (iter->second)
|
|
{
|
|
case ParamAny:
|
|
if (!boost::icontains(instance->getName(), iter->first) && !boost::icontains(instance->getClassNameStr(), iter->first))
|
|
return false;
|
|
break;
|
|
case ParamName:
|
|
if (!boost::icontains(instance->getName(), iter->first))
|
|
return false;
|
|
break;
|
|
case ParamType:
|
|
if (!boost::icontains(instance->getClassNameStr(), iter->first))
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RobloxTreeWidget::filterWidgetRecursive(shared_ptr<RBX::Instance> instance, FilterDeque_SPTR& previousFilterStack, const RBX::Time& startTime)
|
|
{
|
|
if (!RobloxTreeWidgetItem::isExplorerItem(instance))
|
|
return false;
|
|
|
|
bool parentOfShownItem = false;
|
|
|
|
if (instance->getChildren())
|
|
{
|
|
shared_ptr<RBX::Instance> startingChild;
|
|
bool stackFrontFound = true;
|
|
|
|
//using previousFilterStack to start from last search location
|
|
if (!previousFilterStack.empty())
|
|
{
|
|
stackFrontFound = false;
|
|
|
|
startingChild = previousFilterStack.front();
|
|
previousFilterStack.pop_front();
|
|
|
|
//if stack is broken, we continue through all children
|
|
if (startingChild->getParent() != instance.get())
|
|
{
|
|
previousFilterStack.clear();
|
|
stackFrontFound = true;
|
|
}
|
|
}
|
|
|
|
for (RBX::Instances::const_iterator iter = instance->getChildren()->begin(); iter != instance->getChildren()->end(); ++iter)
|
|
{
|
|
if (!stackFrontFound && *iter == startingChild)
|
|
stackFrontFound = true;
|
|
|
|
m_currentFilterStack.push_back(*iter);
|
|
|
|
if (stackFrontFound && filterWidgetRecursive(*iter, previousFilterStack, startTime))
|
|
parentOfShownItem = true;
|
|
|
|
if (isSearchTimeUp(startTime, FInt::StudioTreeWidgetFilterTime))
|
|
break;
|
|
|
|
m_currentFilterStack.pop_back();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
previousFilterStack.clear();
|
|
}
|
|
|
|
//This is a check to see if filter is empty or instance is the datamodel
|
|
if (isFilterEmpty() || !instance->getParent())
|
|
{
|
|
//Restore item to it's original state
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(instance.get()))
|
|
item->setViewState();
|
|
return false;
|
|
}
|
|
|
|
if (filterInstance(instance))
|
|
{
|
|
m_foundItems.insert(instance);
|
|
return true;
|
|
}
|
|
else if (parentOfShownItem)
|
|
{
|
|
m_ancestorsOfFoundItems.insert(instance);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//This is hiding the item if there is a filter
|
|
if (RobloxTreeWidgetItem* item = findItemFromInstance(instance.get()))
|
|
item->setViewState();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RobloxTreeWidget::setWidgetFilter(const QString& filterString)
|
|
{
|
|
m_currentFilter = filterString;
|
|
|
|
QStringList filterList = filterString.split(" ", QString::SkipEmptyParts);
|
|
|
|
bool nextIsName = false;
|
|
bool nextIsType = false;
|
|
m_searchParamList.clear();
|
|
|
|
for (QStringList::const_iterator iter = filterList.begin(); iter != filterList.end(); ++iter)
|
|
{
|
|
if (nextIsName)
|
|
{
|
|
nextIsName = false;
|
|
m_searchParamList.push_back(StringParamPair(iter->toStdString(), ParamName));
|
|
}
|
|
else if (nextIsType)
|
|
{
|
|
nextIsType = false;
|
|
m_searchParamList.push_back(StringParamPair(iter->toStdString(), ParamType));
|
|
}
|
|
else if (iter->startsWith(nameSearchString.c_str(), Qt::CaseInsensitive))
|
|
{
|
|
QString rightSide = iter->right(iter->size() - nameSearchString.size());
|
|
|
|
if (rightSide.isEmpty())
|
|
nextIsName = true;
|
|
else
|
|
m_searchParamList.push_back(StringParamPair(rightSide.toStdString(), ParamName));
|
|
}
|
|
else if (iter->startsWith(classSearchString.c_str(), Qt::CaseInsensitive))
|
|
{
|
|
QString rightSide = iter->right(iter->size() - classSearchString.size());
|
|
|
|
if (rightSide.isEmpty())
|
|
nextIsType = true;
|
|
else
|
|
m_searchParamList.push_back(StringParamPair(rightSide.toStdString(), ParamType));
|
|
}
|
|
else
|
|
{
|
|
m_searchParamList.push_back(StringParamPair(iter->toStdString(), ParamAny));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RobloxTreeWidget::isFoundItem(shared_ptr<RBX::Instance> instance)
|
|
{
|
|
return m_foundItems.count(instance) > 0;
|
|
}
|
|
|
|
bool RobloxTreeWidget::isAncestorOfFoundItem(shared_ptr<RBX::Instance> instance)
|
|
{
|
|
return m_ancestorsOfFoundItems.count(instance) > 0;
|
|
}
|
|
|
|
bool RobloxTreeWidget::isValidSelection(RBX::Instance *pInstance)
|
|
{
|
|
if (!pInstance)
|
|
return false;
|
|
|
|
if (!pInstance->getParent())
|
|
return true;
|
|
|
|
if (isValidSelection(pInstance->getParent()))
|
|
{
|
|
if (!RobloxTreeWidgetItem::isExplorerItem(shared_from(pInstance)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RobloxTreeWidget::onItemSelectionChanged()
|
|
{
|
|
if (m_bIgnoreItemSelectionChanged)
|
|
return;
|
|
|
|
RBX::Selection* pSelection(getSelection());
|
|
if (!pSelection)
|
|
return;
|
|
|
|
if (m_pInstanceSelectionHandler)
|
|
{
|
|
boost::shared_ptr<RBX::Instance> selectedInstance;
|
|
QList<QTreeWidgetItem *> selectedItems = this->selectedItems();
|
|
if (selectedItems.size() > 0)
|
|
{
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(selectedItems.at(0));
|
|
if (pTreeWidgetItem)
|
|
selectedInstance = pTreeWidgetItem->getInstance();
|
|
}
|
|
m_pInstanceSelectionHandler->onInstanceSelected(selectedInstance);
|
|
return;
|
|
}
|
|
|
|
RBX::ScopedAssign<bool> ignoreSelectionEvent(m_bIgnoreInstanceSelectionChanged, true);
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
|
|
|
|
QList<QTreeWidgetItem *> selectedItems = this->selectedItems();
|
|
|
|
shared_ptr<RBX::Instances> instances(new RBX::Instances());
|
|
for (QList<QTreeWidgetItem*>::const_iterator iter = selectedItems.begin(); iter != selectedItems.end(); ++iter)
|
|
{
|
|
if (RobloxTreeWidgetItem* pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(*iter))
|
|
{
|
|
if (shared_ptr<RBX::Instance> instance = pTreeWidgetItem->getInstance())
|
|
instances->push_back(instance);
|
|
}
|
|
}
|
|
|
|
pSelection->setSelection(instances);
|
|
|
|
if (currentItem())
|
|
{
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(currentItem());
|
|
QString className = pTreeWidgetItem->getInstance()->getClassName().c_str();
|
|
if (FFlag::StudioNewWiki)
|
|
className.prepend("API:Class/");
|
|
Q_EMIT(helpTopicChanged(className));
|
|
}
|
|
|
|
//HACK: to get toolbars updated for object selection
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
}
|
|
|
|
void RobloxTreeWidget::modifyItemsSelectionState(InstanceList_PTR &instancesList, bool select)
|
|
{
|
|
QList<QModelIndex> modelIndexes;
|
|
|
|
for (InstanceList_PTR::const_iterator iter = instancesList.begin(); iter != instancesList.end(); )
|
|
{
|
|
if (RobloxTreeWidgetItem *pTreeWidgetItem = findItemFromInstance(*iter))
|
|
{
|
|
modelIndexes.push_back(indexFromItem(pTreeWidgetItem));
|
|
|
|
if (select)
|
|
{
|
|
m_lastSelectedItem = pTreeWidgetItem;
|
|
instancesList.erase(iter++);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
qStableSort(modelIndexes.begin(), modelIndexes.end());
|
|
selectionModel()->select(mergeModelIndexes(modelIndexes), select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
|
|
}
|
|
|
|
void RobloxTreeWidget::onItemExpanded(QTreeWidgetItem *pItem)
|
|
{
|
|
if (!pItem)
|
|
return;
|
|
|
|
RobloxTreeWidgetItem* robloxTreeWidgetItem = static_cast<RobloxTreeWidgetItem*>(pItem);
|
|
robloxTreeWidgetItem->requestItemExpand();
|
|
}
|
|
|
|
void RobloxTreeWidget::onItemCollapsed(QTreeWidgetItem *pItem)
|
|
{
|
|
if (!pItem)
|
|
return;
|
|
|
|
if (isFilterEmpty())
|
|
pItem->setData(1, ExpandRole, QVariant(false));
|
|
}
|
|
|
|
void RobloxTreeWidget::onItemDoubleClicked(QTreeWidgetItem *pWidgetItem, int)
|
|
{
|
|
if (!pWidgetItem)
|
|
return;
|
|
|
|
static_cast<RobloxTreeWidgetItem*>(pWidgetItem)->handleOpen();
|
|
}
|
|
|
|
void RobloxTreeWidget::addToUpdateList(RobloxTreeWidgetItem *pTreeWidgetItem)
|
|
{
|
|
if (!pTreeWidgetItem)
|
|
return;
|
|
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
|
|
if (pTreeWidgetItem->isItemPendingDeletion())
|
|
return;
|
|
pTreeWidgetItem->setViewState();
|
|
if (existsInRemovalList(pTreeWidgetItem) || !pTreeWidgetItem->isInTreeWidget())
|
|
return;
|
|
|
|
std::pair<TreeWidgetItemList::iterator, bool> retIter = m_itemsToUpdate.insert(pTreeWidgetItem);
|
|
if (retIter.second)
|
|
requestUpdate();
|
|
}
|
|
|
|
void RobloxTreeWidget::removeFromUpdateList(RobloxTreeWidgetItem *pTreeWidgetItem)
|
|
{
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
m_itemsToUpdate.erase(pTreeWidgetItem);
|
|
}
|
|
|
|
void RobloxTreeWidget::requestUpdate()
|
|
{
|
|
if (m_bUpdateRequested)
|
|
return;
|
|
//qt will take ownership of the event so no need to delete
|
|
QApplication::postEvent(this, new RobloxCustomEvent(TREE_WIDGET_UPDATE));
|
|
|
|
m_bUpdateRequested = true;
|
|
}
|
|
|
|
void RobloxTreeWidget::requestViewportUpdate()
|
|
{
|
|
if (m_bViewportUpdateRequested || m_bUpdateRequested)
|
|
return;
|
|
//qt will take ownership of the event so no need to delete
|
|
QApplication::postEvent(this, new RobloxCustomEvent(TREE_WIDGET_VIEWPORT_UPDATE));
|
|
m_bViewportUpdateRequested = true;
|
|
}
|
|
|
|
void RobloxTreeWidget::updateSelectionState(RBX::Instance *pInstance)
|
|
{
|
|
RBX::Selection* pSelection(getSelection());
|
|
if (pSelection && pSelection->isSelected(pInstance))
|
|
{
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
m_unSelectedItems.erase(pInstance);
|
|
m_selectedItems.insert(pInstance);
|
|
}
|
|
|
|
// If items are added after updating the highlighting state for CloudEdit mode, update now
|
|
RBX::Instances::const_iterator selectedIter;
|
|
for (auto& playersSelection : m_prevCloudEditSelection)
|
|
{
|
|
selectedIter = std::find(playersSelection.second->begin(), playersSelection.second->end(), shared_from(pInstance));
|
|
if (selectedIter != playersSelection.second->end())
|
|
{
|
|
if (RobloxTreeWidgetItem* pTreeWidgetItem = findItemFromInstance(pInstance))
|
|
pTreeWidgetItem->addPlayerSelection(playersSelection.first, m_pPlayersDataManager->getPlayerColor(playersSelection.first));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RobloxTreeWidget::event(QEvent* evt)
|
|
{
|
|
bool retVal = false;
|
|
if(evt->type() == TREE_WIDGET_UPDATE)
|
|
{
|
|
onTreeWidgetUpdate();
|
|
m_bUpdateRequested = false;
|
|
retVal = true;
|
|
}
|
|
else if(evt->type() == TREE_WIDGET_FILTER_UPDATE)
|
|
{
|
|
onFilterWidgetUpdate();
|
|
}
|
|
else if(evt->type() == TREE_SCROLL_TO_INSTANCE)
|
|
{
|
|
RobloxCustomEventWithArg *pCustomEvent = dynamic_cast<RobloxCustomEventWithArg*>(evt);
|
|
if (pCustomEvent)
|
|
{
|
|
boost::function<void()>* pFunctionObj = pCustomEvent->m_pEventArg;
|
|
if (pFunctionObj)
|
|
(*pFunctionObj)();
|
|
}
|
|
retVal = true;
|
|
}
|
|
else if (evt->type() == TREE_WIDGET_VIEWPORT_UPDATE)
|
|
{
|
|
m_bViewportUpdateRequested = false;
|
|
viewport()->update();
|
|
|
|
retVal = true;
|
|
}
|
|
else if ( evt->type() == QEvent::ShortcutOverride )
|
|
{
|
|
retVal = QTreeWidget::event(evt);
|
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(evt);
|
|
QChar key = keyEvent->key();
|
|
|
|
// capture single key alpha numerical presses - we want these to be considered
|
|
// as shortcuts in the tree to jumped to items starting with that key, not global shortcuts
|
|
if ( !(keyEvent->key() >= Qt::Key_F1 && keyEvent->key() <= Qt::Key_F35) &&
|
|
key.isLetterOrNumber() &&
|
|
keyEvent->modifiers() == Qt::NoModifier )
|
|
{
|
|
keyEvent->accept();
|
|
retVal = true;
|
|
}
|
|
}
|
|
else
|
|
retVal = QTreeWidget::event(evt);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void RobloxTreeWidget::onTreeWidgetUpdate()
|
|
{
|
|
if (isHidden() || m_filterRunning)
|
|
return;
|
|
|
|
QCoreApplication::removePostedEvents(this, TREE_WIDGET_VIEWPORT_UPDATE);
|
|
|
|
//prevents screen flicker
|
|
setUpdatesEnabled(false);
|
|
RBX::ScopedAssign<bool> ignoreSelectionEvent(m_bIgnoreItemSelectionChanged, true);
|
|
|
|
QMutexLocker treeLock(treeWidgetMutex());
|
|
|
|
if (!m_itemsToRemove.empty())
|
|
processChildrenRemoval();
|
|
|
|
int selectionSize = m_selectedItems.size();
|
|
//modify selection first, items not removed from list and items that have yet been populated
|
|
{
|
|
blockSignals(true);
|
|
QMutexLocker lock(treeWidgetMutex());
|
|
|
|
if (!m_unSelectedItems.empty())
|
|
{
|
|
modifyItemsSelectionState(m_unSelectedItems, false);
|
|
m_unSelectedItems.clear();
|
|
}
|
|
|
|
if (!m_selectedItems.empty())
|
|
modifyItemsSelectionState(m_selectedItems, true);
|
|
|
|
selectionSize = m_selectedItems.size();
|
|
blockSignals(false);
|
|
}
|
|
|
|
bool itemsAdded = false;
|
|
|
|
InstanceList_PTR itemsToSelect;
|
|
|
|
RBX::Time startTime = RBX::Time::now<RBX::Time::Fast>();
|
|
|
|
std::vector<RobloxTreeWidgetItem*> recentlyAddedItems;
|
|
|
|
//Processing Selection
|
|
while (!m_selectedItems.empty() && isFilterEmpty() && !isSearchTimeUp(startTime, FInt::StudioTreeWidgetProcessingTime))
|
|
{
|
|
RBX::Instance* pInstance = *m_selectedItems.begin();
|
|
|
|
RBX::Instance* pChild = pInstance;
|
|
while (pChild->getParent() && !findItemFromInstance(pChild->getParent()))
|
|
pChild = pChild->getParent();
|
|
|
|
RobloxTreeWidgetItem* newChildNode = NULL;
|
|
|
|
if (RobloxTreeWidgetItem* pParentItem = findItemFromInstance(pChild->getParent()))
|
|
{
|
|
pParentItem->requestItemExpand();
|
|
newChildNode = pParentItem->processChildAdd(shared_from(pChild));
|
|
}
|
|
else
|
|
{
|
|
m_selectedItems.erase(pInstance);
|
|
}
|
|
|
|
if (pChild == pInstance && newChildNode)
|
|
{
|
|
recentlyAddedItems.push_back(newChildNode);
|
|
m_lastSelectedItem = newChildNode;
|
|
m_selectedItems.erase(pInstance);
|
|
}
|
|
}
|
|
|
|
//Processing items requesting update
|
|
while (!m_itemsToUpdate.empty() && !isSearchTimeUp(startTime, FInt::StudioTreeWidgetProcessingTime))
|
|
{
|
|
RobloxTreeWidgetItem* itemNode = *m_itemsToUpdate.begin();
|
|
|
|
if (itemNode)
|
|
{
|
|
itemNode->updateFilterItems();
|
|
if (itemNode->processChildrenAdd())
|
|
itemsAdded = true;
|
|
|
|
if (!itemNode->hasChildrenToProcess())
|
|
{
|
|
removeFromUpdateList(itemNode);
|
|
itemNode->setDirty(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
//recently added items
|
|
if (!recentlyAddedItems.empty())
|
|
{
|
|
QList<QModelIndex> modelIndexes;
|
|
|
|
for (std::vector<RobloxTreeWidgetItem*>::const_iterator iter = recentlyAddedItems.begin(); iter != recentlyAddedItems.end(); ++iter)
|
|
modelIndexes.push_back(indexFromItem(*iter));
|
|
|
|
qStableSort(modelIndexes.begin(), modelIndexes.end());
|
|
selectionModel()->select(mergeModelIndexes(modelIndexes), QItemSelectionModel::Select);
|
|
}
|
|
|
|
processChildrenAdd();
|
|
|
|
setUpdatesEnabled(true);
|
|
|
|
m_bUpdateRequested = false;
|
|
|
|
//again request for an update
|
|
if (!m_itemsToRemove.empty() || !m_itemsToUpdate.empty() || itemsAdded || (!m_selectedItems.empty() && isFilterEmpty()))
|
|
{
|
|
QTimer::singleShot(0, this, SLOT(requestUpdate()));
|
|
}
|
|
else
|
|
{
|
|
if (m_lastSelectedItem)
|
|
{
|
|
scrollToItem(m_lastSelectedItem);
|
|
eraseLastSelectedItem();
|
|
}
|
|
|
|
if (m_savedMarkerItem)
|
|
QMetaObject::invokeMethod(this, "scrollBackToLastMarkedLocation");
|
|
|
|
Q_EMIT finishedProcessing();
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidget::requestDelete()
|
|
{
|
|
m_bDeletionRequested = true;
|
|
|
|
deActivate();
|
|
|
|
m_pInstance.reset();
|
|
m_pDataModel.reset();
|
|
setPlayersDataManager(boost::shared_ptr<PlayersDataManager>());
|
|
|
|
deleteLater();
|
|
}
|
|
|
|
void RobloxTreeWidget::mousePressEvent(QMouseEvent * evt)
|
|
{
|
|
QTreeWidget::mousePressEvent(evt);
|
|
|
|
RobloxTreeWidgetItem* pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(itemAt(evt->pos()));
|
|
|
|
if (pTreeWidgetItem)
|
|
{
|
|
QString className = pTreeWidgetItem->getInstance()->getClassName().c_str();
|
|
if (FFlag::StudioNewWiki)
|
|
className.prepend("API:Class/");
|
|
Q_EMIT(helpTopicChanged(className));
|
|
}
|
|
|
|
// Check if ContextMenu was opened in the middle of dragging.
|
|
if (m_pRubberBand)
|
|
{
|
|
m_pRubberBand->hide();
|
|
m_pRubberBand->deleteLater();
|
|
m_pRubberBand = NULL;
|
|
}
|
|
else
|
|
{
|
|
//although it will be set to NULL in mouseReleaseEvent but just to be sure setting it to NULL again
|
|
m_pRubberBand = NULL;
|
|
}
|
|
|
|
|
|
//if we cannot drag select then continue with Qt event propagation
|
|
if (!canDragSelect(evt))
|
|
{
|
|
if (FFlag::StudioMimeDataContainsInstancePath)
|
|
{
|
|
QDrag *drag = new QDrag(this);
|
|
QMimeData *mimeData = new QMimeData;
|
|
mimeData->setText(("Game." + pTreeWidgetItem->getInstance()->getFullName()).c_str());
|
|
drag->setMimeData(mimeData);
|
|
drag->exec();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//begin drag select
|
|
m_initialOffset = QPoint(horizontalOffset(), verticalOffset());
|
|
m_rubberBandOrigin = evt->pos();
|
|
|
|
m_pRubberBand = new QRubberBand(QRubberBand::Rectangle, this);
|
|
m_pRubberBand->setGeometry(QRect(m_rubberBandOrigin, QSize()));
|
|
m_pRubberBand->show();
|
|
}
|
|
|
|
void RobloxTreeWidget::mouseMoveEvent(QMouseEvent * evt)
|
|
{
|
|
if (!m_pRubberBand)
|
|
{
|
|
QTreeWidget::mouseMoveEvent(evt);
|
|
if (m_pInstanceSelectionHandler)
|
|
{
|
|
boost::shared_ptr<RBX::Instance> hoveredInstance;
|
|
RobloxTreeWidgetItem* pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(itemAt(evt->pos()));
|
|
if (pTreeWidgetItem)
|
|
hoveredInstance = pTreeWidgetItem->getInstance();
|
|
m_pInstanceSelectionHandler->onInstanceHovered(hoveredInstance);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//--- drag selection is ON (do the required updates)
|
|
if (verticalScrollBar())
|
|
{
|
|
if (evt->y() <= 1)
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + evt->y() - 1);
|
|
else if (evt->y() >= (viewport()->height() - 1))
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + evt->y() - viewport()->height() - 1);
|
|
}
|
|
|
|
if (horizontalScrollBar())
|
|
{
|
|
if (evt->x() <= 1)
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + evt->x() - 1);
|
|
else if (evt->y() >= (viewport()->width() - 1))
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + evt->x() - viewport()->width() - 1);
|
|
}
|
|
|
|
QPoint offset = QPoint(horizontalOffset() - m_initialOffset.x(), verticalOffset() - m_initialOffset.y());
|
|
QRect rubberBandRect = QRect(m_rubberBandOrigin - offset, evt->pos()).normalized();
|
|
|
|
m_pRubberBand->setGeometry(rubberBandRect);
|
|
setSelection(QRect(rubberBandRect.bottomLeft(), rubberBandRect.topLeft()), QItemSelectionModel::ClearAndSelect);
|
|
}
|
|
|
|
void RobloxTreeWidget::mouseReleaseEvent(QMouseEvent * evt)
|
|
{
|
|
if (!m_pRubberBand)
|
|
{
|
|
QTreeWidget::mouseReleaseEvent(evt);
|
|
return;
|
|
}
|
|
|
|
//clean up rubberband
|
|
m_pRubberBand->hide();
|
|
m_pRubberBand->deleteLater();
|
|
m_pRubberBand = NULL;
|
|
}
|
|
|
|
bool RobloxTreeWidget::canDragSelect(QMouseEvent * evt)
|
|
{
|
|
if (evt->buttons() != Qt::LeftButton || evt->modifiers() != Qt::NoModifier || m_pInstanceSelectionHandler)
|
|
return false;
|
|
|
|
QPoint mousePos = evt->pos();
|
|
QTreeWidgetItem *pTreeWidgetItem = itemAt(mousePos);
|
|
if (pTreeWidgetItem)
|
|
{
|
|
// - make sure if the mouse is clicked outside the item label we must be able to drag select
|
|
// - we must not activate drag selection if mouse click is on the left of the label (to ensure the expand keys work fine)
|
|
// - item drag/drop will work when user clicks on the item label
|
|
QRect itemRect = visualItemRect(pTreeWidgetItem);
|
|
if ( !itemRect.contains(mousePos) ||
|
|
(itemRect.contains(mousePos) && (mousePos.x() < (itemRect.bottomLeft().x() + 28 + fontMetrics().width(pTreeWidgetItem->text(0))))) )
|
|
return false;
|
|
}
|
|
|
|
//-- we can do a drag select --
|
|
|
|
// item under mouse is already selected and the only selected item, we don't proceed further
|
|
if (pTreeWidgetItem && pTreeWidgetItem->isSelected() && (selectedItems().size() == 1))
|
|
return true;
|
|
|
|
//clear previously selected items
|
|
clearSelection();
|
|
//if we have an item then select it
|
|
if (pTreeWidgetItem)
|
|
pTreeWidgetItem->setSelected(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
void RobloxTreeWidget::dragEnterEvent(QDragEnterEvent *evt)
|
|
{
|
|
const QMimeData *mime = evt->mimeData();
|
|
QString data = mime->text();
|
|
QTreeWidget::dragEnterEvent(evt);
|
|
}
|
|
|
|
void RobloxTreeWidget::dragMoveEvent(QDragMoveEvent * evt)
|
|
{
|
|
// call base class to handle auto scrolling
|
|
QTreeWidget::dragMoveEvent(evt);
|
|
|
|
evt->ignore();
|
|
|
|
if (RobloxTreeWidgetItem *pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(itemAt(evt->pos())))
|
|
{
|
|
if (shared_ptr<RBX::Instance> pInstance = pTreeWidgetItem->getInstance())
|
|
{
|
|
RBX::Selection* selection = getSelection();
|
|
for (RBX::Instances::const_iterator iter = selection->begin(); iter != selection->end(); ++iter)
|
|
if (pInstance == *iter || (*iter)->isAncestorOf2(pInstance) || (*iter)->getIsParentLocked())
|
|
return;
|
|
|
|
evt->acceptProposedAction();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void setChildren(RBX::Instance* pParent, shared_ptr<RBX::Instance> pChild);
|
|
|
|
void RobloxTreeWidget::dropEvent(QDropEvent * evt)
|
|
{
|
|
try
|
|
{
|
|
RobloxTreeWidgetItem *pTreeWidgetItem = dynamic_cast<RobloxTreeWidgetItem*>(itemAt(evt->pos()));
|
|
if (pTreeWidgetItem)
|
|
{
|
|
boost::shared_ptr<RBX::Instance> pInstance = pTreeWidgetItem->getInstance();
|
|
if (pInstance)
|
|
{
|
|
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
|
|
pTreeWidgetItem->requestItemExpand();
|
|
|
|
std::for_each(getSelection()->begin(), getSelection()->end(), boost::bind(&setChildren, pInstance.get(), _1));
|
|
|
|
//add in history for undo/redo
|
|
RBX::ChangeHistoryService::requestWaypoint("Drop", m_pDataModel.get());
|
|
m_pDataModel->setDirty(true);
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setText(e.what());
|
|
msgBox.exec();
|
|
}
|
|
|
|
evt->setDropAction(Qt::IgnoreAction);
|
|
setState(NoState);
|
|
}
|
|
|
|
static void setChildren(RBX::Instance* pParent, shared_ptr<RBX::Instance> pChild)
|
|
{
|
|
//self drag case (ideally qt should have already taken care of this)
|
|
if (!pChild || pChild->getParent() == pParent)
|
|
return;
|
|
|
|
pChild->setParent(pParent);
|
|
}
|
|
|
|
static int nearestWidgetItemIndex(RobloxTreeWidgetItem *pParentWidgetItem, int first, int last, int itemTypeToAdd)
|
|
{
|
|
while (first <= last)
|
|
{
|
|
int mid = (first + last) / 2;
|
|
RobloxTreeWidgetItem *pCurrentItem = static_cast<RobloxTreeWidgetItem*>(pParentWidgetItem->child(mid));
|
|
RBXASSERT(pCurrentItem != NULL);
|
|
|
|
if (itemTypeToAdd == pCurrentItem->itemType())
|
|
return mid;
|
|
else if (itemTypeToAdd > pCurrentItem->itemType())
|
|
first = mid+1;
|
|
else
|
|
last = mid-1;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
static QItemSelection mergeModelIndexes(const QList<QModelIndex> &indexes)
|
|
{
|
|
QItemSelection colSpans;
|
|
// merge columns
|
|
int i = 0;
|
|
while (i < indexes.count()) {
|
|
QModelIndex tl = indexes.at(i);
|
|
QModelIndex br = tl;
|
|
while (++i < indexes.count()) {
|
|
QModelIndex next = indexes.at(i);
|
|
if ((next.parent() == br.parent())
|
|
&& (next.row() == br.row())
|
|
&& (next.column() == br.column() + 1))
|
|
br = next;
|
|
else
|
|
break;
|
|
}
|
|
colSpans.append(QItemSelectionRange(tl, br));
|
|
}
|
|
// merge rows
|
|
QItemSelection rowSpans;
|
|
i = 0;
|
|
while (i < colSpans.count()) {
|
|
QModelIndex tl = colSpans.at(i).topLeft();
|
|
QModelIndex br = colSpans.at(i).bottomRight();
|
|
QModelIndex prevTl = tl;
|
|
while (++i < colSpans.count()) {
|
|
QModelIndex nextTl = colSpans.at(i).topLeft();
|
|
QModelIndex nextBr = colSpans.at(i).bottomRight();
|
|
|
|
if (nextTl.parent() != tl.parent())
|
|
break;
|
|
|
|
if ((nextTl.column() == prevTl.column()) && (nextBr.column() == br.column())
|
|
&& (nextTl.row() == prevTl.row() + 1) && (nextBr.row() == br.row() + 1)) {
|
|
br = nextBr;
|
|
prevTl = nextTl;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
rowSpans.append(QItemSelectionRange(tl, br));
|
|
}
|
|
return rowSpans;
|
|
}
|
|
|
|
void RobloxTreeWidget::setInstanceSelectionHandler(InstanceSelectionHandler* handler)
|
|
{
|
|
setMouseTracking(handler);
|
|
m_pInstanceSelectionHandler = handler;
|
|
}
|
|
|
|
void RobloxTreeWidget::markLocationToSuppressScrolling()
|
|
{
|
|
if (m_savedMarkerItem == NULL)
|
|
{
|
|
m_savedMarkerItem = getMarkerItem();
|
|
}
|
|
}
|
|
|
|
void RobloxTreeWidget::scrollBackToLastMarkedLocation()
|
|
{
|
|
if (m_savedMarkerItem)
|
|
{
|
|
QTreeWidgetItem* currenMarkerItem = getMarkerItem();
|
|
if (currenMarkerItem != m_savedMarkerItem)
|
|
{
|
|
int savedRow = indexFromItem(m_savedMarkerItem).row();
|
|
int currentRow = indexFromItem(currenMarkerItem).row();
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + (savedRow - currentRow) * verticalScrollBar()->singleStep());
|
|
}
|
|
m_savedMarkerItem = NULL;
|
|
}
|
|
}
|
|
|
|
QTreeWidgetItem* RobloxTreeWidget::getMarkerItem()
|
|
{
|
|
QList<QTreeWidgetItem*> selection = selectedItems();
|
|
// if we've a single selection and the selected item is not hidden, the use it as benchmark
|
|
if (selection.count() == 1 && !selection.at(0)->isHidden())
|
|
return selection.at(0);
|
|
// or else use the top item as benchmark
|
|
return itemAt(rect().topLeft());
|
|
}
|
|
|
|
void RobloxTreeWidget::setPlayersDataManager(boost::shared_ptr<PlayersDataManager> playersDataManager)
|
|
{
|
|
if (m_pPlayersDataManager == playersDataManager)
|
|
return;
|
|
|
|
if (m_pPlayersDataManager)
|
|
disconnect(m_pPlayersDataManager.get(), SIGNAL(cloudEditSelectionChanged(int)), this, SLOT(onCloudEditSelectionChanged(int)));
|
|
m_TreeWidgetDelegate.reset();
|
|
m_pPlayersDataManager = playersDataManager;
|
|
|
|
if (m_pPlayersDataManager)
|
|
{
|
|
m_TreeWidgetDelegate.reset(new TreeWidgetDelegate(this));
|
|
setItemDelegate(m_TreeWidgetDelegate.get());
|
|
connect(m_pPlayersDataManager.get(), SIGNAL(cloudEditSelectionChanged(int)), this, SLOT(onCloudEditSelectionChanged(int)));
|
|
}
|
|
}
|
|
|
|
// TODO: Merge this with eraseLastSelectedItem
|
|
void RobloxTreeWidget::itemDeletionRequested(RobloxTreeWidgetItem* pItem)
|
|
{
|
|
if (pItem == m_savedMarkerItem)
|
|
m_savedMarkerItem = NULL;
|
|
}
|
|
|
|
RobloxExplorerWidget::RobloxExplorerWidget(QWidget *pParent)
|
|
: QWidget(pParent)
|
|
, m_robloxTreeWidget(NULL)
|
|
, m_sendingCounter(false)
|
|
{
|
|
m_lineEdit = new QLineEdit();
|
|
m_lineEdit->setPlaceholderText(QString("Filter workspace (%1)").arg(UpdateUIManager::Instance().getMainWindow().explorerFilterAction->shortcut().toString()));
|
|
m_lineEdit->hide();
|
|
|
|
m_loadingMovie = new QMovie(":/images/loading.gif");
|
|
m_loadingLabel = new QLabel(m_lineEdit);
|
|
m_loadingLabel->setMovie(m_loadingMovie);
|
|
|
|
QHBoxLayout* layout = new QHBoxLayout();
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addStretch();
|
|
layout->addWidget(m_loadingLabel);
|
|
layout->addSpacing(5);
|
|
|
|
m_lineEdit->setLayout(layout);
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout();
|
|
mainLayout->setSpacing(4);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainLayout->addWidget(m_lineEdit);
|
|
setLayout(mainLayout);
|
|
|
|
setFocusProxy(m_lineEdit);
|
|
}
|
|
|
|
void RobloxExplorerWidget::setCurrentWidget(RobloxTreeWidget* treeWidget)
|
|
{
|
|
if (m_robloxTreeWidget == treeWidget)
|
|
return;
|
|
|
|
QLayout* mainLayout = layout();
|
|
|
|
if (m_robloxTreeWidget)
|
|
{
|
|
m_lineEdit->hide();
|
|
|
|
onProcessingFinished();
|
|
|
|
m_robloxTreeWidget->hide();
|
|
mainLayout->removeWidget(m_robloxTreeWidget);
|
|
|
|
disconnect(m_robloxTreeWidget, SIGNAL(finishedProcessing()), this, SLOT(onProcessingFinished()));
|
|
disconnect(m_robloxTreeWidget, SIGNAL(startedProcessing()), this, SLOT(onProcessingStarted()));
|
|
disconnect(m_robloxTreeWidget, SIGNAL(focusGained()), this, SIGNAL(focusGained()));
|
|
disconnect(m_lineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterTreeWidget(QString)));
|
|
}
|
|
|
|
m_robloxTreeWidget = treeWidget;
|
|
|
|
if (m_robloxTreeWidget)
|
|
{
|
|
mainLayout->addWidget(m_robloxTreeWidget);
|
|
m_robloxTreeWidget->show();
|
|
m_lineEdit->show();
|
|
|
|
m_lineEdit->setText(m_robloxTreeWidget->currentFilter());
|
|
|
|
connect(m_robloxTreeWidget, SIGNAL(finishedProcessing()), this, SLOT(onProcessingFinished()));
|
|
connect(m_robloxTreeWidget, SIGNAL(startedProcessing()), this, SLOT(onProcessingStarted()));
|
|
connect(m_robloxTreeWidget, SIGNAL(focusGained()), this, SIGNAL(focusGained()));
|
|
connect(m_lineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterTreeWidget(QString)));
|
|
|
|
if (!m_robloxTreeWidget->isFilterEmpty())
|
|
{
|
|
onProcessingStarted();
|
|
m_robloxTreeWidget->filterWidget();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobloxExplorerWidget::onProcessingStarted()
|
|
{
|
|
m_loadingMovie->start();
|
|
m_loadingLabel->show();
|
|
}
|
|
|
|
void RobloxExplorerWidget::onProcessingFinished()
|
|
{
|
|
m_loadingLabel->hide();
|
|
m_loadingMovie->stop();
|
|
}
|
|
|
|
void RobloxExplorerWidget::filterTreeWidget(const QString& text)
|
|
{
|
|
if (m_robloxTreeWidget)
|
|
{
|
|
if (!m_sendingCounter)
|
|
{
|
|
m_sendingCounter = true;
|
|
QTimer::singleShot(1000, this, SLOT(sendWidgetCounter()));
|
|
}
|
|
|
|
m_robloxTreeWidget->filterWidget(text);
|
|
}
|
|
}
|
|
|
|
void RobloxExplorerWidget::sendWidgetCounter()
|
|
{
|
|
m_sendingCounter = false;
|
|
|
|
RobloxSettings settings;
|
|
bool previouslyFiltered = settings.value("ExplorerWidgetFilterUsed", false).toBool();
|
|
settings.setValue("ExplorerWidgetFilterUsed", true);
|
|
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, previouslyFiltered ? "ExplorerWidgetFilterUsed" : "ExplorerWidgetFilterUsed_FTU");
|
|
}
|