/** * DebuggerWidgets.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" // Qt Headers #include #include #include #include #include #include #include #include #include #include #include #include // Roblox Headers #include "util/StandardOut.h" #include "util/ScopedAssign.h" #include "util/BrickColor.h" #include "util/UDim.h" #include "script/Script.h" #include "v8datamodel/DataModel.h" #include "V8DataModel/ChangeHistory.h" #include "reflection/Type.h" // Roblox Studio Headers #include "DebuggerWidgets.h" #include "DebuggerClient.h" #include "UpdateUIManager.h" #include "RobloxMainWindow.h" #include "QtUtilities.h" #include "RobloxSettings.h" #include "RobloxDocManager.h" //TODO: Move this to generic header file static bool populateValues(QTreeWidgetItem *pParentItem, const QString &key, const RBX::Reflection::Variant varValue); static QTreeWidgetItem* addChildren(QTreeWidgetItem* pParentItem, int iconIndex = 6); //-------------------------------------------------------------------------------------------- // DebuggerViewsManager //-------------------------------------------------------------------------------------------- DebuggerViewsManager& DebuggerViewsManager::Instance() { static DebuggerViewsManager instance; return instance; } DebuggerViewsManager::DebuggerViewsManager() : m_bBreakpointEncountered(false) { //TODO: Move dockwidget initialization to UpdateUiManager.cpp RobloxMainWindow* rbxMainWindow = &UpdateUIManager::Instance().getMainWindow(); m_DockWidgets[eDV_BREAKPOINTS] = new QDockWidget("Breakpoints", rbxMainWindow); m_DockWidgets[eDV_BREAKPOINTS]->setObjectName("breakpointsDockWidget"); m_DockWidgets[eDV_CALLSTACK] = new QDockWidget("Call Stack", rbxMainWindow); m_DockWidgets[eDV_CALLSTACK]->setObjectName("callStackDockWidget"); m_DockWidgets[eDV_WATCH] = new QDockWidget("Watch", rbxMainWindow); m_DockWidgets[eDV_WATCH]->setObjectName("watchDockWidget"); m_ViewWidgets[eDV_BREAKPOINTS] = new BreakpointsWidget; m_ViewWidgets[eDV_CALLSTACK] = new CallStackWidget; m_ViewWidgets[eDV_WATCH] = new WatchWidget; //set widgets for (int dockId = 0; dockId < eDV_MAX; dockId++) { m_DockWidgets[(EDebuggerViewID)dockId]->setWidget(m_ViewWidgets[(EDebuggerViewID)dockId]); } //restore views RobloxSettings settings; for (int dockId = 0; dockId < eDV_MAX; dockId++) { if(!rbxMainWindow->restoreDockWidget(m_DockWidgets[(EDebuggerViewID)dockId])) { if (dockId == 0) rbxMainWindow->addDockWidget(Qt::BottomDockWidgetArea, m_DockWidgets[(EDebuggerViewID)dockId]); else rbxMainWindow->tabifyDockWidget(m_DockWidgets[(EDebuggerViewID)(dockId - 1)], m_DockWidgets[(EDebuggerViewID)dockId]); } } //by default hide widgets QAction* pToggleViewAction = NULL; QString icons[eDV_MAX]; icons[eDV_BREAKPOINTS] = QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/breakpoints.png"); icons[eDV_CALLSTACK] = QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/callStack.png"); icons[eDV_WATCH] = QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/watch.png"); for (int ii = 0; ii < eDV_MAX; ii++) { EDebuggerViewID dockId = (EDebuggerViewID)ii; m_DockWidgets[dockId]->setVisible(false); pToggleViewAction = m_DockWidgets[dockId]->toggleViewAction(); if (pToggleViewAction) { pToggleViewAction->setIcon(QPixmap(icons[dockId])); pToggleViewAction->setIconVisibleInMenu(false); if (rbxMainWindow->isRibbonStyle()) { QString actionName(m_DockWidgets[dockId]->objectName()+"Action"); pToggleViewAction->setObjectName(actionName); UpdateUIManager::defaultCommands.append(" "); UpdateUIManager::defaultCommands.append(actionName); rbxMainWindow->addAction(pToggleViewAction); } else { rbxMainWindow->viewToolsToolBar->addAction(pToggleViewAction); } } } // m_cConnections.push_back(DebuggerClientManager::Instance().debuggingStarted.connect(boost::bind(&DebuggerViewsManager::onDebuggingStarted, this))); m_cConnections.push_back(DebuggerClientManager::Instance().debuggingStopped.connect(boost::bind(&DebuggerViewsManager::onDebuggingStopped, this))); //if debugger client get's changed (update dock widgets) m_cConnections.push_back(DebuggerClientManager::Instance().clientActivated.connect(boost::bind(&DebuggerViewsManager::onDebuggerClientActivated, this, _1, _2))); m_cConnections.push_back(DebuggerClientManager::Instance().clientDeactivated.connect(boost::bind(&DebuggerViewsManager::onDebuggerClientDeactivated, this))); //update dock widgets whenever exeuction gets paused or started m_cConnections.push_back(DebuggerClientManager::Instance().executionDataCleared.connect(boost::bind(&DebuggerViewsManager::onExecutionDataCleared, this))); m_cConnections.push_back(DebuggerClientManager::Instance().breakpointEncountered.connect(boost::bind(&DebuggerViewsManager::onBreakpointEncountered, this, _1, _2))); //clear all data m_cConnections.push_back(DebuggerClientManager::Instance().clearAll.connect(boost::bind(&DebuggerViewsManager::onClearAll, this))); //breakpoint m_cConnections.push_back(DebuggerClientManager::Instance().breakpointAdded.connect(boost::bind(&DebuggerViewsManager::onBreakpointAdded, this, _1))); m_cConnections.push_back(DebuggerClientManager::Instance().breakpointRemoved.connect(boost::bind(&DebuggerViewsManager::onBreakpointRemoved, this, _1))); //watch m_cConnections.push_back(DebuggerClientManager::Instance().watchAdded.connect(boost::bind(&DebuggerViewsManager::onWatchAdded, this, _1))); m_cConnections.push_back(DebuggerClientManager::Instance().watchRemoved.connect(boost::bind(&DebuggerViewsManager::onWatchRemoved, this, _1))); //debuggers paused with delay m_cConnections.push_back(DebuggerClientManager::Instance().debuggersListUpdated.connect(boost::bind(&DebuggerViewsManager::onDebuggersListUpdated, this, _1))); } void DebuggerViewsManager::showDockWidget(EDebuggerViewID dockId) { QDockWidget* pDockWidget = m_DockWidgets[dockId]; if (pDockWidget) { pDockWidget->show(); pDockWidget->raise(); } } void DebuggerViewsManager::restoreViews() { //TODO: show only the last shown views for (int dockId = 1; dockId < eDV_MAX; dockId++) { m_DockWidgets[(EDebuggerViewID)dockId]->show(); } } void DebuggerViewsManager::saveViews() { //TODO: save currently shown views for (int dockId = 1; dockId < eDV_MAX; dockId++) { m_DockWidgets[(EDebuggerViewID)dockId]->close(); } } void DebuggerViewsManager::onDebuggingStarted() { m_bBreakpointEncountered = false; } void DebuggerViewsManager::onDebuggingStopped() { for (int dockId = 1; dockId < eDV_MAX; dockId++) { QMetaObject::invokeMethod(m_DockWidgets[(EDebuggerViewID)dockId], "close", Qt::QueuedConnection); } } void DebuggerViewsManager::onDebuggerClientActivated(int currentBreakpoint, RBX::Scripting::ScriptDebugger::Stack currentStack) { getViewWidget(eDV_BREAKPOINTS).setCurrentBreakpoint(currentBreakpoint); getViewWidget(eDV_WATCH).updateWatchValues(true); } void DebuggerViewsManager::onDebuggerClientDeactivated() { // deactivate watch values getViewWidget(eDV_WATCH).updateWatchValues(false); } void DebuggerViewsManager::onExecutionDataCleared() { getViewWidget(eDV_CALLSTACK).clearDebuggers(); getViewWidget(eDV_CALLSTACK).clear(); getViewWidget(eDV_BREAKPOINTS).setCurrentBreakpoint(-1); getViewWidget(eDV_WATCH).updateWatchValues(false); } void DebuggerViewsManager::onBreakpointEncountered(int currentBreakpointLine, DebuggerClient* pDebuggerClient) { if (!m_bBreakpointEncountered) { for (int dockId = 1; dockId < eDV_MAX; dockId++) { QMetaObject::invokeMethod(m_DockWidgets[(EDebuggerViewID)dockId], "show", Qt::QueuedConnection); } m_bBreakpointEncountered = true; } getViewWidget(eDV_CALLSTACK).populateDebuggers(pDebuggerClient); getViewWidget(eDV_BREAKPOINTS).setCurrentBreakpoint(currentBreakpointLine); getViewWidget(eDV_WATCH).updateWatchValues(true); } void DebuggerViewsManager::onClearAll() { getViewWidget(eDV_CALLSTACK).clearDebuggers(); getViewWidget(eDV_CALLSTACK).clear(); getViewWidget(eDV_BREAKPOINTS).clear(); getViewWidget(eDV_WATCH).clear(); for (int dockId = 1; dockId < eDV_MAX; dockId++) { m_DockWidgets[(EDebuggerViewID)dockId]->close(); } } void DebuggerViewsManager::onBreakpointAdded(shared_ptr spBreakpoint) { getViewWidget(eDV_BREAKPOINTS).addBreakpoint(spBreakpoint); } void DebuggerViewsManager::onBreakpointRemoved(shared_ptr spBreakpoint) { getViewWidget(eDV_BREAKPOINTS).removeBreakpoint(spBreakpoint); } void DebuggerViewsManager::onWatchAdded(shared_ptr spWatch) { getViewWidget(eDV_WATCH).addWatch(spWatch); } void DebuggerViewsManager::onWatchRemoved(shared_ptr spWatch) { getViewWidget(eDV_WATCH).removeWatch(spWatch); } void DebuggerViewsManager::onDebuggersListUpdated(DebuggerClient* pDebuggerClient) { getViewWidget(eDV_CALLSTACK).populateDebuggers(pDebuggerClient); } //-------------------------------------------------------------------------------------------- // NoEditDelegate //-------------------------------------------------------------------------------------------- class NoEditDelegate: public QStyledItemDelegate { public: NoEditDelegate(QObject* parent=0):QStyledItemDelegate(parent) {;} virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { return NULL; } }; //-------------------------------------------------------------------------------------------- // DebuggerTreeWidget //-------------------------------------------------------------------------------------------- DebuggerTreeWidget::DebuggerTreeWidget(QWidget* pParent) : QTreeWidget(pParent) { setUniformRowHeights(true); setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSelectionBehavior(QAbstractItemView::SelectRows); setItemDelegate(new NoEditDelegate(this)); connect(this, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(onItemActivated(QTreeWidgetItem*))); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemActivated(QTreeWidgetItem*))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(onItemChanged(QTreeWidgetItem*, int))); } void DebuggerTreeWidget::onItemActivated(QTreeWidgetItem* pItem) { DebuggerTreeWidgetItem* pDebuggerItem = dynamic_cast(pItem); if(pDebuggerItem) pDebuggerItem->activate(); } void DebuggerTreeWidget::onItemChanged(QTreeWidgetItem* pItem, int column) { DebuggerTreeWidgetItem* pDebuggerItem = dynamic_cast(pItem); if(pDebuggerItem) pDebuggerItem->columnModified(column); } void DebuggerTreeWidget::mousePressEvent(QMouseEvent *evt) { if (!itemAt(evt->pos())) setCurrentItem(NULL); else QTreeWidget::mousePressEvent(evt); } void DebuggerTreeWidget::keyPressEvent(QKeyEvent * evt) { if (evt->key() == Qt::Key_Delete) { Q_EMIT deleteSelectedItem(); evt->accept(); return; } QTreeWidget::keyPressEvent(evt); } //-------------------------------------------------------------------------------------------- // StackDetailsItem //-------------------------------------------------------------------------------------------- StackDetailsItem::StackDetailsItem(const RBX::Scripting::ScriptDebugger::FunctionInfo& functionInfo, bool isFirstItem) : m_bIsFirstItem(isFirstItem) { setData(1, Qt::DisplayRole, QVariant(functionInfo.frame)); setText(2, QString::fromStdString(functionInfo.what)); setText(3, QString::fromStdString(functionInfo.name)); setData(4, Qt::DisplayRole, QVariant(functionInfo.currentline)); setText(5, QString::fromStdString(functionInfo.short_src)); setText(6, QString::fromStdString(functionInfo.namewhat)); if (m_bIsFirstItem) setIcon(0, QIcon(":/16x16/images/Studio 2.0 icons/16x16/debugger_arrow.png")); m_spAssociatedScript = functionInfo.script; } StackDetailsItem::~StackDetailsItem() { DebuggerClient* pDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_spAssociatedScript); if (pDebuggerClient) pDebuggerClient->setMarker(getLine() - 1, "", false); } void StackDetailsItem::activate() { updateIcon(true); DebuggerClient* pDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_spAssociatedScript); if (pDebuggerClient) pDebuggerClient->setCurrentFrame(getFrame()); } void StackDetailsItem::updateIcon(bool isCurrent) { DebuggerClient* pDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_spAssociatedScript); if (!pDebuggerClient) return; //do not update anything in case of it's the first frame if (m_bIsFirstItem) { pDebuggerClient->setMarker(getLine() - 1, ":/16x16/images/Studio 2.0 icons/16x16/debugger_arrow.png", true); pDebuggerClient->setCurrentLine(getLine() - 1); return; } pDebuggerClient->setMarker(getLine() - 1, isCurrent ? ":/16x16/images/Studio 2.0 icons/16x16/debugger_arrow_curve_left.png" : "", true); setIcon(0, QPixmap(isCurrent ? ":/16x16/images/Studio 2.0 icons/16x16/debugger_arrow_curve_left.png" : "")); } int StackDetailsItem::getLine() { return data(4, Qt::DisplayRole).toInt(); } int StackDetailsItem::getFrame() { return data(1, Qt::DisplayRole).toInt(); } //-------------------------------------------------------------------------------------------- // CallStackTreeWidget //-------------------------------------------------------------------------------------------- CallStackTreeWidget::CallStackTreeWidget(QWidget* pParent) : DebuggerTreeWidget(pParent) , m_pPreviousItem(NULL) { setWindowTitle("Call Stack"); setSortingEnabled(false); QStringList headerLabels; headerLabels<<" "<<"Frame"<<"What"<<"Function Name"<<"Line No."<<"Source"<<"Function Explanation"; setHeaderLabels(headerLabels); header()->setResizeMode(0,QHeaderView::Interactive); header()->setStretchLastSection(true); } CallStackTreeWidget::~CallStackTreeWidget() { } void CallStackTreeWidget::setCallStack(const RBX::Scripting::ScriptDebugger::Stack& stack) { m_pPreviousItem = NULL; clear(); for(RBX::Scripting::ScriptDebugger::Stack::const_iterator iter = stack.begin(); iter != stack.end(); ++iter) addTopLevelItem(new StackDetailsItem(*iter, iter==stack.begin())); if (!stack.empty()) { m_pPreviousItem = topLevelItem(0); static_cast(m_pPreviousItem)->updateIcon(true); } header()->resizeSections(QHeaderView::ResizeToContents); } void CallStackTreeWidget::onItemActivated(QTreeWidgetItem* pItem) { StackDetailsItem *pStackDetailsItem = dynamic_cast(m_pPreviousItem); if (pStackDetailsItem) pStackDetailsItem->updateIcon(false); DebuggerTreeWidget::onItemActivated(pItem); m_pPreviousItem = pItem; setCurrentItem(NULL); } //-------------------------------------------------------------------------------------------- // CallStackDebuggersList //-------------------------------------------------------------------------------------------- CallStackDebuggersList::CallStackDebuggersList(QWidget* pWidget) : QComboBox(pWidget) , m_bIsDirty(false) { } void CallStackDebuggersList::showPopup() { Q_EMIT popupAboutToShow(); QComboBox::showPopup(); } //-------------------------------------------------------------------------------------------- // CallStackWidget //-------------------------------------------------------------------------------------------- CallStackWidget::CallStackWidget(QWidget* pParent) : m_pCurrentDebuggerClient(NULL) { m_pCallStackTreeWidget = new CallStackTreeWidget(this); QLabel* pLabel = new QLabel("Current Script: ", this); m_pScriptDebuggersList = new CallStackDebuggersList(this); connect(m_pScriptDebuggersList, SIGNAL(popupAboutToShow()), this, SLOT(onDebuggerListPopupShow())); //m_pScriptDebuggersList->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); m_pScriptDebuggersList->setMinimumWidth(200); m_pScriptDebuggersList->setMaximumWidth(QWIDGETSIZE_MAX); QSpacerItem *pHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); QHBoxLayout* pComboLabelLayout = new QHBoxLayout; pComboLabelLayout->addWidget(pLabel); pComboLabelLayout->addWidget(m_pScriptDebuggersList); pComboLabelLayout->addItem(pHorizontalSpacer); QGridLayout *pGridLayout = new QGridLayout(this); pGridLayout->setSpacing(4); pGridLayout->setContentsMargins(0, 0, 0, 0); pGridLayout->addLayout(pComboLabelLayout, 0, 0, 1, 1); pGridLayout->addWidget(m_pCallStackTreeWidget, 1, 0, 1, 1); } void CallStackWidget::populateDebuggers(DebuggerClient* pDebuggerClient) { // if there's no change in debugger client then mark debuggers list as dirty and return if ( DebuggerClientManager::Instance().getCurrentDebuggerClient() && (m_pCurrentDebuggerClient == pDebuggerClient) && (m_pScriptDebuggersList->count() > 0) ) { static_cast(m_pScriptDebuggersList)->setDirty(true); return; } QString scriptName; DebuggerClient* currentDebuggerClient = NULL; boost::shared_ptr script; bool indexSet = false; m_pCurrentDebuggerClient = pDebuggerClient; // first clear list widget clearDebuggers(); clear(); // now update combo and selection of index will update list widget const std::vector& debuggerClients = DebuggerClientManager::Instance().getDebuggerClients(); for (unsigned int ii=0; ii < debuggerClients.size(); ++ii) { currentDebuggerClient = debuggerClients[ii]; if (!currentDebuggerClient || !RBX::Scripting::DebuggerManager::singleton().findDebugger(currentDebuggerClient->getScript().get())) continue; script = currentDebuggerClient->getScript(); if (!script) continue; scriptName = script->getFullName().c_str(); const RBX::Scripting::ScriptDebugger::PausedThreads& pausedThreads = currentDebuggerClient->getPausedThreads(); for (RBX::Scripting::ScriptDebugger::PausedThreads::const_iterator it=pausedThreads.begin(); it!=pausedThreads.end(); ++it) { m_pScriptDebuggersList->addItem(QString("%1 (%2) - %3").arg(scriptName).arg(it->first, 1, 16).arg(it->second.pausedLine), QVariant::fromValue((void*)script.get())); if ((currentDebuggerClient == m_pCurrentDebuggerClient) && (currentDebuggerClient->getCurrentThread() == it->first)) { currentDebuggerClient->setCurrentThread(currentDebuggerClient->getCurrentThread()); m_pScriptDebuggersList->setCurrentIndex(m_pScriptDebuggersList->count() - 1); setCallStack(currentDebuggerClient->getCurrentCallStack()); indexSet = true; } } } // if index is not set then make sure we set an index if (!indexSet && m_pScriptDebuggersList->count() > 0) onCurrentIndexChanged(0); connect(m_pScriptDebuggersList, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); } void CallStackWidget::clearDebuggers() { disconnect(m_pScriptDebuggersList, SIGNAL(currentIndexChanged(int)), this, SLOT(onCurrentIndexChanged(int))); m_pScriptDebuggersList->clear(); static_cast(m_pScriptDebuggersList)->setDirty(false); } void CallStackWidget::setCallStack(const RBX::Scripting::ScriptDebugger::Stack& stack) { m_pCallStackTreeWidget->setCallStack(stack); } void CallStackWidget::clear() { m_pCallStackTreeWidget->clear(); } void CallStackWidget::onCurrentIndexChanged(int index) { QVariant var = m_pScriptDebuggersList->itemData(index); boost::shared_ptr spScript = shared_from(static_cast(var.value())); if (spScript) { DebuggerClient* pDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(spScript); if (pDebuggerClient) { // update current thread QString text = m_pScriptDebuggersList->itemText(index); QRegExp exp("\\((.*)\\)"); if(exp.indexIn(text) >= 0) { int threadID = exp.cap(1).toInt(0, 16); pDebuggerClient->setCurrentThread(threadID); } if (pDebuggerClient->updateDocument()) setCallStack(pDebuggerClient->getCurrentCallStack()); // if document has been updated correctly, set current callstack else clear(); // else clear the previous stack details } } } void CallStackWidget::onDebuggerListPopupShow() { // check if we need to update debuggers list if (!static_cast(m_pScriptDebuggersList)->isDirty()) return; // force debuggers list population! clearDebuggers(); populateDebuggers(DebuggerClientManager::Instance().getCurrentDebuggerClient()); } //-------------------------------------------------------------------------------------------- // BreakpointItem //-------------------------------------------------------------------------------------------- BreakpointItem::BreakpointItem(boost::shared_ptr spBreakpoint) : m_spBreakpoint(spBreakpoint) , m_bIgnoreBreakpointModification(false) , m_bIgnoreWaypointRequest(false) { setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemIsEditable); setCheckState(0, m_spBreakpoint->isEnabled() ? Qt::Checked : Qt::Unchecked); int line = m_spBreakpoint->getLine(); setData(1, Qt::DisplayRole, QVariant(line)); RBX::Scripting::ScriptDebugger* spDebugger = RBX::Instance::fastDynamicCast(spBreakpoint->getParent()); if (spDebugger) { setText(2, spDebugger->getScript()->getFullName().c_str()); if (DebuggerClient *pClient = DebuggerClientManager::Instance().getDebuggerClient(shared_from(spDebugger->getScript()))) setText(3, pClient->getSourceCodeAtLine(line).trimmed()); } //setText(4, QString::fromStdString(m_spBreakpoint->getCondition())); m_cPropertyChangedConnection = m_spBreakpoint->propertyChangedSignal.connect(boost::bind(&BreakpointItem::onBreakpointPropertyChanged, this, _1)); m_cAncestoryChangedConnection = m_spBreakpoint->ancestryChangedSignal.connect(boost::bind(&BreakpointItem::onScriptAncestryChanged, this, _1)); } BreakpointItem::~BreakpointItem() { m_cPropertyChangedConnection.disconnect(); m_cAncestoryChangedConnection.disconnect(); } bool BreakpointItem::hasBreakpoint(boost::shared_ptr spBreakpoint) { return (spBreakpoint == m_spBreakpoint); } void BreakpointItem::columnModified(int column) { RBX::ScopedAssign ignoreBreakpointModificationEvent(m_bIgnoreBreakpointModification, true); if (column == 0) { RBX::DataModel::LegacyLock lock(DebuggerClientManager::Instance().getDataModel(), RBX::DataModelJob::Write); RBX::Scripting::DebuggerBreakpoint::prop_Enabled.setValue(m_spBreakpoint.get(), checkState(column) == Qt::Checked); // update status in script doc // since this is the only way to update enabled/disabled state of a breakpoint, so we can avoid using property signal DebuggerClientManager::Instance().syncBreakpointState(m_spBreakpoint); } else if (column == 4) { QString modifiedCondition = text(3); if(modifiedCondition != QString::fromStdString(m_spBreakpoint->getCondition())) { RBX::DataModel::LegacyLock lock(DebuggerClientManager::Instance().getDataModel(), RBX::DataModelJob::Write); RBX::Scripting::DebuggerBreakpoint::prop_Condition.setValue(m_spBreakpoint.get(), modifiedCondition.toStdString()); } } } void BreakpointItem::activate() { //TODO: Check if we need to highlight line with a different color? RBX::Scripting::ScriptDebugger* pDebugger = RBX::Instance::fastDynamicCast(m_spBreakpoint->getParent()); if (pDebugger) { RobloxDocManager::Instance().openDoc(LuaSourceBuffer::fromInstance(shared_from(pDebugger->getScript()))); DebuggerClient* pDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(shared_from(pDebugger->getScript())); if (pDebuggerClient) pDebuggerClient->setCurrentLine(m_spBreakpoint->getLine() - 1); } } void BreakpointItem::deleteBreakpoint() { RBX::DataModel::LegacyLock lock(DebuggerClientManager::Instance().getDataModel(), RBX::DataModelJob::Write); m_spBreakpoint->setParent(NULL); m_spBreakpoint.reset(); } void BreakpointItem::setBreakpointEnabled(bool state) { RBX::ScopedAssign ignoreBreakpointModificationEvent(m_bIgnoreWaypointRequest, true); setCheckState(0, state ? Qt::Checked : Qt::Unchecked); } bool BreakpointItem::isBreakpointEnabled() { return checkState(0) == Qt::Checked; } void BreakpointItem::onBreakpointPropertyChanged(const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor) { if (m_bIgnoreBreakpointModification) return; //TODO: make sure this is called from Main Thread (issues can happen if breakpoints are set from a script) if (*pPropertyDescriptor == RBX::Scripting::DebuggerBreakpoint::prop_Enabled) setBreakpointEnabled(m_spBreakpoint->isEnabled()); } void BreakpointItem::onScriptAncestryChanged(shared_ptr newParent) { // update breakpoint name if the script has change in hierarchy if (newParent) { RBX::Scripting::ScriptDebugger* spDebugger = RBX::Instance::fastDynamicCast(m_spBreakpoint->getParent()); if (spDebugger) setText(2, spDebugger->getScript()->getFullName().c_str()); } } //-------------------------------------------------------------------------------------------- // BreakpointsTreeWidget //-------------------------------------------------------------------------------------------- BreakpointsTreeWidget::BreakpointsTreeWidget(QWidget* pParent) : DebuggerTreeWidget(pParent) { //set columns QStringList headerLabels; headerLabels<<"Enabled"<<"Line"<<"Script Name"<<"Source Line";//<<"Condition"; setHeaderLabels(headerLabels); header()->setResizeMode(0,QHeaderView::Interactive); header()->setStretchLastSection(true); //setItemDelegateForColumn(3, new QStyledItemDelegate(this)); //enable sorting sortByColumn(2, Qt::AscendingOrder); setSortingEnabled(true); } void BreakpointsTreeWidget::addBreakpoint(boost::shared_ptr spBreakpoint) { if(spBreakpoint && !findBreakpointItem(spBreakpoint)) addTopLevelItem( new BreakpointItem(spBreakpoint) ); } void BreakpointsTreeWidget::removeBreakpoint(boost::shared_ptr spBreakpoint) { if(spBreakpoint) { BreakpointItem *pBPItem = findBreakpointItem(spBreakpoint); if(pBPItem) delete pBPItem; } } void BreakpointsTreeWidget::setCurrentBreakpoint(int breakpointLine) { if (breakpointLine < 0) { clearSelection(); } else { BreakpointItem *pBPItem = findBreakpointItem(breakpointLine); if (pBPItem) { scrollToItem(pBPItem); setCurrentItem(pBPItem); } } } void BreakpointsTreeWidget::deleteSelectedBreakpoint() { QList selItems = selectedItems(); if (selItems.length() > 0) { BreakpointItem* pBreakpointItem = dynamic_cast(selItems[0]); if (pBreakpointItem) { pBreakpointItem->deleteBreakpoint(); delete pBreakpointItem; } } } void BreakpointsTreeWidget::deleteAllBreakpoints() { int numChild = topLevelItemCount(); if (numChild <=0) return; int currentChild = 0; BreakpointItem* pBreakpointItem = NULL; while (currentChild < numChild) { pBreakpointItem = static_cast(topLevelItem(currentChild)); if (pBreakpointItem) pBreakpointItem->deleteBreakpoint(); ++currentChild; } clear(); } void BreakpointsTreeWidget::disableEnableAllBreakpoints() { int numChild = topLevelItemCount(); if (numChild <=0) return; int currentChild = 0; BreakpointItem* pBreakpointItem = NULL; bool breakpointEnabled = hasBreakpointsEnabled(); while (currentChild < numChild) { pBreakpointItem = static_cast(topLevelItem(currentChild)); if (pBreakpointItem && (breakpointEnabled == pBreakpointItem->isBreakpointEnabled())) pBreakpointItem->setBreakpointEnabled(!breakpointEnabled); ++currentChild; } } void BreakpointsTreeWidget::goToSourceCode() { QList selItems = selectedItems(); if (selItems.length() > 0) { BreakpointItem* pBreakpointItem = static_cast(selItems[0]); if (pBreakpointItem) pBreakpointItem->activate(); } } BreakpointItem* BreakpointsTreeWidget::findBreakpointItem(boost::shared_ptr spBreakpoint) { int numChild = topLevelItemCount(), currentChild = 0; BreakpointItem* pBreakpointItem = NULL; while (currentChild < numChild) { //static cast should be OK as we are inserting only BreakpointItem type of items! pBreakpointItem = static_cast(topLevelItem(currentChild)); if (pBreakpointItem && pBreakpointItem->hasBreakpoint(spBreakpoint)) return pBreakpointItem; ++currentChild; } return NULL; } BreakpointItem* BreakpointsTreeWidget::findBreakpointItem(int breakpointLine) { int numChild = topLevelItemCount(), currentChild = 0; BreakpointItem* pBreakpointItem = NULL; while (currentChild < numChild) { //static cast should be OK as we are inserting only BreakpointItem type of items! pBreakpointItem = static_cast(topLevelItem(currentChild)); if ( pBreakpointItem && (pBreakpointItem->data(1, Qt::DisplayRole).toInt() == breakpointLine) ) return pBreakpointItem; ++currentChild; } return NULL; } bool BreakpointsTreeWidget::hasBreakpointsEnabled() { int numChild = topLevelItemCount(), currentChild = 0; BreakpointItem* pBreakpointItem = NULL; while (currentChild < numChild) { //static cast should be OK as we are inserting only BreakpointItem type of items! pBreakpointItem = static_cast(topLevelItem(currentChild)); if ( pBreakpointItem && pBreakpointItem->isBreakpointEnabled() ) return true; ++currentChild; } return false; } //-------------------------------------------------------------------------------------------- // BreakpointsWidget //-------------------------------------------------------------------------------------------- BreakpointsWidget::BreakpointsWidget(QWidget* pParent) : QWidget(pParent) , m_bPushButtonStateUpdateRequested(false) , m_bIgnoreBreakpointModification(false) { QSizePolicy pushButtonSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); pushButtonSizePolicy.setHorizontalStretch(0); pushButtonSizePolicy.setVerticalStretch(0); m_pDeleteBreakpointTB = new QToolButton(this); m_pDeleteBreakpointTB->setObjectName(QString::fromUtf8("DeleteBreakpointPB")); m_pDeleteBreakpointTB->setSizePolicy(pushButtonSizePolicy); m_pDeleteBreakpointTB->setToolTip(tr("Delete Selected Breakpoint")); m_pDeleteBreakpointTB->setEnabled(false); QIcon icon; icon.addFile(QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/breakpointDelete.png"), QSize(), QIcon::Normal, QIcon::Off); m_pDeleteBreakpointTB->setIcon(icon); QFrame *pLine1 = new QFrame(this); pLine1->setFrameShape(QFrame::VLine); pLine1->setFrameShadow(QFrame::Sunken); m_pDeleteAllBreakpointsTB = new QToolButton(this); m_pDeleteAllBreakpointsTB->setObjectName(QString::fromUtf8("DeleteAllBreakpointsPB")); m_pDeleteAllBreakpointsTB->setSizePolicy(pushButtonSizePolicy); m_pDeleteAllBreakpointsTB->setToolTip(tr("Delete All Breakpoints")); QIcon icon1; icon1.addFile(QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/deleteAllBreakpoints.png"), QSize(), QIcon::Normal, QIcon::Off); m_pDeleteAllBreakpointsTB->setIcon(icon1); m_pDeleteAllBreakpointsTB->setEnabled(false); m_pDisableEnableAllBreakpointsTB = new QToolButton(this); m_pDisableEnableAllBreakpointsTB->setObjectName(QString::fromUtf8("DisableEnableAllBreakpointsPB")); m_pDisableEnableAllBreakpointsTB->setSizePolicy(pushButtonSizePolicy); QIcon icon2; icon2.addFile(QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/enableDisableBreakpoints.png"), QSize(), QIcon::Normal, QIcon::Off); m_pDisableEnableAllBreakpointsTB->setIcon(icon2); m_pDisableEnableAllBreakpointsTB->setEnabled(false); QFrame *pLine2 = new QFrame(this); pLine2->setFrameShape(QFrame::VLine); pLine2->setFrameShadow(QFrame::Sunken); m_pGoToSourceCodeTB = new QToolButton(this); m_pGoToSourceCodeTB->setObjectName(QString::fromUtf8("GoToSourceCodeTB")); m_pGoToSourceCodeTB->setSizePolicy(pushButtonSizePolicy); QIcon icon3; icon3.addFile(QString::fromUtf8(":/16x16/images/Studio 2.0 icons/16x16/gotoSource.png"), QSize(), QIcon::Normal, QIcon::Off); m_pGoToSourceCodeTB->setIcon(icon3); m_pGoToSourceCodeTB->setToolTip(tr("Go To Source Code")); m_pGoToSourceCodeTB->setEnabled(false); QSpacerItem *pHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); QHBoxLayout *pHorizontalLayout = new QHBoxLayout(); pHorizontalLayout->setSpacing(4); pHorizontalLayout->addWidget(m_pDeleteBreakpointTB); pHorizontalLayout->addWidget(pLine1); pHorizontalLayout->addWidget(m_pDeleteAllBreakpointsTB); pHorizontalLayout->addWidget(m_pDisableEnableAllBreakpointsTB); pHorizontalLayout->addWidget(pLine2); pHorizontalLayout->addWidget(m_pGoToSourceCodeTB); pHorizontalLayout->addItem(pHorizontalSpacer); m_pBreakpointTreeWidget = new BreakpointsTreeWidget(this); QGridLayout *pGridLayout = new QGridLayout(this); pGridLayout->setVerticalSpacing(4); pGridLayout->setContentsMargins(0, 0, 0, 0); pGridLayout->addLayout(pHorizontalLayout, 0, 0, 1, 1); pGridLayout->addWidget(m_pBreakpointTreeWidget, 1, 0, 1, 1); connect(m_pDeleteBreakpointTB, SIGNAL(clicked()), this, SLOT(onPushButtonClicked())); connect(m_pDeleteAllBreakpointsTB, SIGNAL(clicked()), this, SLOT(onPushButtonClicked())); connect(m_pDisableEnableAllBreakpointsTB, SIGNAL(clicked()), this, SLOT(onPushButtonClicked())); connect(m_pGoToSourceCodeTB, SIGNAL(clicked()), this, SLOT(onPushButtonClicked())); connect(UpdateUIManager::Instance().getMainWindow().toggleAllBreakpointsStateAction, SIGNAL(triggered()), m_pDisableEnableAllBreakpointsTB, SIGNAL(clicked())); connect(m_pBreakpointTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(requestButtonsStatusUpdate())); connect(m_pBreakpointTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(requestButtonsStatusUpdate())); connect(m_pBreakpointTreeWidget, SIGNAL(deleteSelectedItem()), this, SLOT(onDeleteSelectedItem())); } void BreakpointsWidget::addBreakpoint(boost::shared_ptr breakpoint) { if (!m_bIgnoreBreakpointModification) m_pBreakpointTreeWidget->addBreakpoint(breakpoint); requestButtonsStatusUpdate(); } void BreakpointsWidget::removeBreakpoint(boost::shared_ptr breakpoint) { if (!m_bIgnoreBreakpointModification) m_pBreakpointTreeWidget->removeBreakpoint(breakpoint); requestButtonsStatusUpdate(); } void BreakpointsWidget::setCurrentBreakpoint(int breakpointLine) { m_pBreakpointTreeWidget->setCurrentBreakpoint(breakpointLine); requestButtonsStatusUpdate(); } void BreakpointsWidget::clear() { m_pBreakpointTreeWidget->clear(); requestButtonsStatusUpdate(); } void BreakpointsWidget::onPushButtonClicked() { QToolButton *pButtonClicked = qobject_cast(sender()); if (!pButtonClicked) return; RBX::ScopedAssign ignorePropertyModifyEvent(m_bIgnoreBreakpointModification, true); if (pButtonClicked == m_pDeleteBreakpointTB) { m_pBreakpointTreeWidget->deleteSelectedBreakpoint(); } else if (pButtonClicked == m_pDeleteAllBreakpointsTB) { QtUtilities::RBXMessageBox msgBox; msgBox.setIcon(QMessageBox::Question); msgBox.setText(tr("Do you want to delete all breakpoints?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); if ( msgBox.exec() != QMessageBox::Yes ) return; m_pBreakpointTreeWidget->deleteAllBreakpoints(); } else if (pButtonClicked == m_pDisableEnableAllBreakpointsTB) { m_pBreakpointTreeWidget->disableEnableAllBreakpoints(); } else if (pButtonClicked == m_pGoToSourceCodeTB) { m_pBreakpointTreeWidget->goToSourceCode(); } requestButtonsStatusUpdate(); } void BreakpointsWidget::onDeleteSelectedItem() { RBX::ScopedAssign ignorePropertyModifyEvent(m_bIgnoreBreakpointModification, true); m_pBreakpointTreeWidget->deleteSelectedBreakpoint(); } void BreakpointsWidget::requestButtonsStatusUpdate() { if (m_bPushButtonStateUpdateRequested) return; m_bPushButtonStateUpdateRequested = true; QTimer::singleShot(0, this, SLOT(updateButtonsStatus())); } void BreakpointsWidget::updateButtonsStatus() { int numItems = m_pBreakpointTreeWidget->topLevelItemCount(); if (numItems < 1) { //disable all buttons m_pDeleteBreakpointTB->setEnabled(false); m_pDeleteAllBreakpointsTB->setEnabled(false); m_pDisableEnableAllBreakpointsTB->setEnabled(false); m_pGoToSourceCodeTB->setEnabled(false); //update connected action also UpdateUIManager::Instance().getMainWindow().toggleAllBreakpointsStateAction->setEnabled(false); } else { //enable m_pDeleteAllBreakpointsTB button if we have any item m_pDeleteAllBreakpointsTB->setEnabled(true); //if we have any enabled breakpoint then enable m_pDeleteAllBreakpointsTB m_pDisableEnableAllBreakpointsTB->setEnabled(true); if (m_pBreakpointTreeWidget->hasBreakpointsEnabled()) m_pDisableEnableAllBreakpointsTB->setToolTip(tr("Disable All Breakpoints")); else m_pDisableEnableAllBreakpointsTB->setToolTip(tr("Enable All Breakpoints")); //update connected action also UpdateUIManager::Instance().getMainWindow().toggleAllBreakpointsStateAction->setEnabled(true); //if there are selected items, then enable m_pBreakpointTreeWidget and m_pGoToSourceCodeTB int numSelected = m_pBreakpointTreeWidget->selectedItems().length(); m_pDeleteBreakpointTB->setEnabled(numSelected > 0); m_pGoToSourceCodeTB->setEnabled(numSelected > 0); } m_bPushButtonStateUpdateRequested = false; } //-------------------------------------------------------------------------------------------- // WatchItem //-------------------------------------------------------------------------------------------- WatchItem::WatchItem(boost::shared_ptr spWatch) : m_spWatch(spWatch) , m_bIsInValid(true) { setFlags(flags() | Qt::ItemIsEditable); if (m_spWatch) { setText(0, QString::fromStdString(m_spWatch->getCondition())); onScriptAncestryChanged(shared_from(m_spWatch->getParent())); m_cPropertyChangedConnection = m_spWatch->propertyChangedSignal.connect(boost::bind(&WatchItem::onWatchPropertyChanged, this, _1)); m_cAncestoryChangedConnection = m_spWatch->ancestryChangedSignal.connect(boost::bind(&WatchItem::onScriptAncestryChanged, this, _1)); } } WatchItem::~WatchItem() { m_cPropertyChangedConnection.disconnect(); m_cAncestoryChangedConnection.disconnect(); } void WatchItem::updateWatchValue() { removeChildren(); if (!m_spWatch) return; bool result = false; try { // TODO: Check if we need to get values from Active DebuggerClient or Current DebuggerClient? RBX::Reflection::Variant Value = DebuggerClientManager::Instance().getWatchValue(m_spWatch); if (!Value.isVoid()) result = populateValues(this, QString::fromStdString(m_spWatch->getCondition()), Value); } catch (std::runtime_error const& exp) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Exception: %s", exp.what()); result = false; } setInvalid(!result); } bool WatchItem::hasWatch(boost::shared_ptr spWatch) { return m_spWatch == spWatch; } void WatchItem::columnModified(int column) { if (!m_spWatch) return; RBX::ScopedAssign ignoreWatchModificationEvent(m_bIgnoreWatchModification, true); if (column == 0) { std::string watchExpression = text(0).toStdString(); if (watchExpression != m_spWatch->getExpression()) { //TODO: Check for syntax of expression RBX::DataModel::LegacyLock lock(DebuggerClientManager::Instance().getDataModel(), RBX::DataModelJob::Write); RBX::Scripting::DebuggerWatch::prop_Expression.setValue(m_spWatch.get(), watchExpression); //also update the value updateWatchValue(); } } } void WatchItem::deleteWatch() { if (!m_spWatch) return; RBX::DataModel::LegacyLock lock(DebuggerClientManager::Instance().getDataModel(), RBX::DataModelJob::Write); m_spWatch->setParent(NULL); m_spWatch.reset(); } void WatchItem::onWatchPropertyChanged(const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor) { if (m_bIgnoreWatchModification || !m_spWatch) return; if (*pPropertyDescriptor == RBX::Scripting::DebuggerWatch::prop_Expression) setText(0, QString::fromStdString(RBX::Scripting::DebuggerWatch::prop_Expression.getValue(m_spWatch.get()))); } void WatchItem::removeChildren() { QList childItems = takeChildren(); QList::iterator iter = childItems.begin(); while (iter != childItems.end()) { delete *iter; ++iter; } childItems.clear(); } void WatchItem::setInvalid(bool state) { m_bIsInValid = state; if (m_bIsInValid) { removeChildren(); setText(1, QString("Error symbol \"%1\" not found").arg(text(0))); } } void WatchItem::onScriptAncestryChanged(shared_ptr newParent) { // update script name if the script has change in hierarchy if (newParent) { QString scriptName; RBX::Scripting::ScriptDebugger* spDebugger = RBX::Instance::fastDynamicCast(m_spWatch->getParent()); if (spDebugger && spDebugger->getScript()) scriptName = spDebugger->getScript()->getFullName().c_str(); setText(2, scriptName); } } class DummyWatchItem: public WatchItem { public: DummyWatchItem() : WatchItem(boost::shared_ptr()) , m_bIgnoreTextModified(false) { } void columnModified(int column) { if (m_bIgnoreTextModified || column != 0) return; QString watchExpression = text(0); if (!watchExpression.isEmpty()) { // add watch DebuggerClientManager::Instance().addWatch(watchExpression); // remove the text entered m_bIgnoreTextModified = true; setText(0, ""); m_bIgnoreTextModified = false; } } void setInvalid(bool state) {} private: bool m_bIgnoreTextModified; }; class WatchItemDelegate : public QStyledItemDelegate { public: WatchItemDelegate(WatchTreeWidget* parent) : QStyledItemDelegate(parent) , m_pWatchTreeWidget(parent) { } QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() != 0) return QStyledItemDelegate::createEditor(parent, option, index); // if we are in any doc other than script doc, then do not enable "DummyWatchItem" DummyWatchItem* dummyWatchItem = dynamic_cast(m_pWatchTreeWidget->itemFromIndex(index)); if (dummyWatchItem) { IRobloxDoc* pDoc = RobloxDocManager::Instance().getCurrentDoc(); if (pDoc && (pDoc->docType() != IRobloxDoc::SCRIPT)) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "Please open a script document to add a new watch expression"); return NULL; } } // create line edit as editor QLineEdit *pLineEdit = new QLineEdit(parent); pLineEdit->setGeometry(option.rect); pLineEdit->setFrame(false); return pLineEdit; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); painter->save(); { painter->setPen(QPen(QColor("lightgray"))); painter->drawLine(option.rect.right(), option.rect.y(), option.rect.right(), option.rect.bottom()); } painter->restore(); } private: WatchTreeWidget* m_pWatchTreeWidget; }; class NoEditItemDelegate : public QStyledItemDelegate { public: NoEditItemDelegate(WatchTreeWidget* parent) : QStyledItemDelegate(parent) { } QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { return NULL; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); painter->save(); { painter->setPen(QPen(QColor("lightgray"))); painter->drawLine(option.rect.right(), option.rect.y(), option.rect.right(), option.rect.bottom()); } painter->restore(); } }; //-------------------------------------------------------------------------------------------- // WatchTreeWidget //-------------------------------------------------------------------------------------------- WatchTreeWidget::WatchTreeWidget(QWidget *pParent) : DebuggerTreeWidget(pParent) , m_pContextualMenu(NULL) , m_pCopyAction(NULL) , m_pAddWatchAction(NULL) , m_pDeleteWatchAction(NULL) , m_pDeleteAllWatchesAction(NULL) , m_bIgnoreWatchModification(false) { //set columns QStringList headerLabels; headerLabels<<"Expression"<<"Value"<<"Script Name"; setHeaderLabels(headerLabels); header()->setResizeMode(0,QHeaderView::Interactive); header()->setResizeMode(1,QHeaderView::Interactive); header()->setStretchLastSection(true); header()->resizeSection(1, 200); //expressions can be modified setItemDelegateForColumn(0, new WatchItemDelegate(this)); //make sure we draw columns correctly, so a no edit item is required setItemDelegateForColumn(1, new NoEditItemDelegate(this)); //required to show child expansion controls (expand/collapse) setRootIsDecorated(true); //add dummywatch item, to add watch inline addDummyWatchItem(); } void WatchTreeWidget::addWatch(boost::shared_ptr spWatch) { if (!m_bIgnoreWatchModification) { if (spWatch && !findWatchItem(spWatch)) { WatchItem* pWatchItem = new WatchItem(spWatch); //insert item before the last item insertTopLevelItem(topLevelItemCount() - 1, pWatchItem); //also update the value pWatchItem->updateWatchValue(); } } } void WatchTreeWidget::removeWatch(boost::shared_ptr spWatch) { if (!m_bIgnoreWatchModification && spWatch) { WatchItem *pWatchItem = findWatchItem(spWatch); if(pWatchItem) delete pWatchItem; } } void WatchTreeWidget::updateWatchValues(bool enable) { try { int numChild = topLevelItemCount(), currentChild = 0; WatchItem* pWatchItem = NULL; setUpdatesEnabled(false); while (currentChild < numChild) { pWatchItem = static_cast(topLevelItem(currentChild)); if (pWatchItem) { if (enable) { pWatchItem->updateWatchValue(); } else { pWatchItem->setInvalid(true); } } ++currentChild; } setUpdatesEnabled(true); } catch (std::runtime_error const& exp) { setUpdatesEnabled(true); RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Exception: %s", exp.what()); } } void WatchTreeWidget::deleteSelectedWatch() { RBX::ScopedAssign ignoreWatchDeleteEvent(m_bIgnoreWatchModification, true); QList selItems = selectedItems(); if (selItems.length() > 0) { // make sure we do not delete DummyWatchItem WatchItem* pWatchItem = dynamic_cast(selItems[0]); DummyWatchItem* pDummyWatchItem = dynamic_cast(selItems[0]); if (pWatchItem && !pDummyWatchItem) { pWatchItem->deleteWatch(); delete pWatchItem; } } } void WatchTreeWidget::copySelectedItem() { QList selItems = selectedItems(); if (selItems.length() > 0) { QTreeWidgetItem* pWatchItem = selItems[0]; if (pWatchItem) { QString textToCopy; textToCopy.append(getFullName(pWatchItem)); textToCopy.append("\t"); textToCopy.append(pWatchItem->text(1)); QClipboard *pClipboard = QApplication::clipboard(); if (pClipboard) pClipboard->setText(textToCopy); } } } void WatchTreeWidget::addWatch() { QList selItems = selectedItems(); QString watchExpression; if (selItems.length() > 0) { //add watch for child QTreeWidgetItem* pWatchItem = selItems[0]; if (pWatchItem) { QString fullName = getFullName(pWatchItem); if (fullName != pWatchItem->text(0)) watchExpression = fullName; } } if (!watchExpression.isEmpty()) { DebuggerClientManager::Instance().addWatch(watchExpression); } else { DummyWatchItem* pDummyWatchItem = dynamic_cast(topLevelItem(topLevelItemCount()-1)); if (pDummyWatchItem) { scrollToItem(pDummyWatchItem); setCurrentIndex(indexFromItem(pDummyWatchItem, 0)); editItem(pDummyWatchItem, 0); } else { // control should not come here! watchExpression = QInputDialog::getText(this, tr("Watch"), tr("Watch Expression:"), QLineEdit::Normal, watchExpression, NULL, Qt::WindowTitleHint | Qt::WindowSystemMenuHint); if (!watchExpression.isEmpty()) DebuggerClientManager::Instance().addWatch(watchExpression); } } } void WatchTreeWidget::removeWatch() { deleteSelectedWatch(); } void WatchTreeWidget::deleteAllWatches() { int numChild = topLevelItemCount(); if (numChild <=0) return; RBX::ScopedAssign ignoreWatchDeleteEvent(m_bIgnoreWatchModification, true); int currentChild = 0; WatchItem* pWatchItem = NULL; while (currentChild < numChild) { pWatchItem = dynamic_cast(topLevelItem(currentChild)); if (pWatchItem) pWatchItem->deleteWatch(); ++currentChild; } clear(); } void WatchTreeWidget::contextMenuEvent(QContextMenuEvent* evt) { if (!m_pContextualMenu) { m_pContextualMenu = new QMenu(this); m_pCopyAction = m_pContextualMenu->addAction(tr("Copy"), this, SLOT(copySelectedItem())); m_pContextualMenu->addSeparator(); m_pAddWatchAction = m_pContextualMenu->addAction(tr("Add Watch"), this, SLOT(addWatch())); m_pDeleteWatchAction = m_pContextualMenu->addAction(tr("Delete Watch"), this, SLOT(removeWatch())); m_pContextualMenu->addSeparator(); m_pDeleteAllWatchesAction = m_pContextualMenu->addAction(tr("Delete All"), this, SLOT(deleteAllWatches())); } m_pDeleteAllWatchesAction->setEnabled(topLevelItemCount() > 0); // allow watch addition only if we are in a script doc IRobloxDoc *pCurrentDoc = RobloxDocManager::Instance().getCurrentDoc(); m_pAddWatchAction->setEnabled(pCurrentDoc && (pCurrentDoc->docType() == IRobloxDoc::SCRIPT)); m_pCopyAction->setEnabled(false); m_pDeleteWatchAction->setEnabled(false); QList selItems = selectedItems(); if (selItems.length() == 1 && dynamic_cast(selItems[0])) { m_pDeleteAllWatchesAction->setEnabled(true); } else if (selItems.length() > 0) { WatchItem* pWatchItem = dynamic_cast(selItems[0]); m_pDeleteWatchAction->setEnabled(pWatchItem && !pWatchItem->parent()); m_pCopyAction->setEnabled(true); } m_pContextualMenu->exec(evt->globalPos()); } void WatchTreeWidget::mousePressEvent(QMouseEvent *event) { DebuggerTreeWidget::mousePressEvent(event); if ((event->button() == Qt::LeftButton) && (event->pos().x() + header()->offset() > 20) && (currentColumn() == 0)) { QTreeWidgetItem* pTreeWidgetItem = itemAt(event->pos()); if (pTreeWidgetItem) { setCurrentIndex(indexFromItem(pTreeWidgetItem, 0)); editItem(pTreeWidgetItem, 0); } } } WatchItem* WatchTreeWidget::findWatchItem(boost::shared_ptr spWatch) { int numChild = topLevelItemCount(), currentChild = 0; WatchItem* pWatchItem = NULL; while (currentChild < numChild) { //static cast should be OK as we are inserting only BreakpointItem type of items! pWatchItem = static_cast(topLevelItem(currentChild)); if (pWatchItem && pWatchItem->hasWatch(spWatch)) return pWatchItem; ++currentChild; } return NULL; } QString WatchTreeWidget::getFullName(QTreeWidgetItem *pItem) { QString fullName; while (pItem) { fullName.prepend(pItem->text(0)); pItem = pItem->parent(); if (pItem) fullName.prepend('.'); } return fullName; } void WatchTreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { // For Ribbon, it get's difficult to differentiate between enabled and disabled items, make the colors darker/lighter bool isRibbon = UpdateUIManager::Instance().getMainWindow().isRibbonStyle(); QStyleOptionViewItemV4 opt = option; WatchItem *pItem = static_cast(itemFromIndex(index)); if (pItem && !pItem->isInvalid()) { // we do not need to do anything for Menu mode if (isRibbon) { opt.palette.setColor(QPalette::All, QPalette::Text, opt.palette.color(QPalette::Active, QPalette::Text).darker()); opt.palette.setColor(QPalette::All, QPalette::Base, opt.palette.color(QPalette::Active, QPalette::Base).darker()); } } else { QColor disabledText(opt.palette.color(QPalette::Disabled, QPalette::Text)); QColor disabledBase(opt.palette.color(QPalette::Disabled, QPalette::Base)); opt.palette.setColor(QPalette::All, QPalette::Text, isRibbon ? QColor("gray") : disabledText); opt.palette.setColor(QPalette::All, QPalette::Base, isRibbon ? QColor("gray") : disabledBase); } QTreeWidget::drawRow(painter, opt, index); painter->save(); { painter->setPen(QPen(QColor("lightgray"))); painter->drawLine(opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); } painter->restore(); } void WatchTreeWidget::clear() { DebuggerTreeWidget::clear(); // add dummy item for inline watch addition addDummyWatchItem(); } void WatchTreeWidget::addDummyWatchItem() { addTopLevelItem(new DummyWatchItem); } //-------------------------------------------------------------------------------------------- // WatchWidget //-------------------------------------------------------------------------------------------- WatchWidget::WatchWidget(QWidget *pParent) : QWidget(pParent) { m_pWatchTreeWidget = new WatchTreeWidget(this); QGridLayout *pGridLayout = new QGridLayout(this); pGridLayout->setSpacing(0); pGridLayout->setContentsMargins(0, 0, 0, 0); pGridLayout->addWidget(m_pWatchTreeWidget, 0, 0, 1, 1); connect(m_pWatchTreeWidget, SIGNAL(deleteSelectedItem()), this, SLOT(onDeleteSelectedItem())); } void WatchWidget::addWatch(shared_ptr spWatch) { m_pWatchTreeWidget->addWatch(spWatch); } void WatchWidget::removeWatch(shared_ptr spWatch) { m_pWatchTreeWidget->removeWatch(spWatch); } void WatchWidget::updateWatchValues(bool enable) { QMetaObject::invokeMethod(m_pWatchTreeWidget, "updateWatchValues", Qt::AutoConnection, Q_ARG(bool, enable)); } void WatchWidget::clear() { m_pWatchTreeWidget->clear(); } void WatchWidget::onDeleteSelectedItem() { m_pWatchTreeWidget->deleteSelectedWatch(); } //-------------------------------------------------------------------------------------------- // DebuggerToolTipWidget //-------------------------------------------------------------------------------------------- DebuggerToolTipWidget::DebuggerToolTipWidget(QWidget *pParent) : QTreeWidget(pParent) , m_pModifiedByMouse(NULL) { setWindowFlags(Qt::ToolTip); setFocusPolicy(Qt::NoFocus); setUniformRowHeights(true); setColumnCount(2); setRootIsDecorated(false); setMouseTracking(true); setStyleSheet("QTreeView::item { border-right: 1px solid black }"); header()->hide(); m_pExpansionTimer = new QTimer(this); m_pExpansionTimer->setSingleShot(true); connect(m_pExpansionTimer, SIGNAL(timeout()), this, SLOT(onTimeout())); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(computeToolTipSize())); connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(computeToolTipSize())); } void DebuggerToolTipWidget::showToolTip(const QPoint& pos, const QString &key, const RBX::Reflection::Variant &value) { //clear previously set data clear(); setRootIsDecorated(false); //now again create the required items QTreeWidgetItem *pTreeWidgetItem = addChildren(NULL); try { populateValues(pTreeWidgetItem, key, value); } catch (...) { return; } m_Key = key; if (pTreeWidgetItem->childCount() > 0) setRootIsDecorated(true); addTopLevelItem(pTreeWidgetItem); computeToolTipSize(); showAt(pos); qApp->installEventFilter(this); } void DebuggerToolTipWidget::hideToolTip() { qApp->removeEventFilter(this); m_pExpansionTimer->stop(); m_pModifiedByMouse = NULL; m_Key = ""; hide(); } void DebuggerToolTipWidget::showAt(const QPoint& pos) { move(pos.x()+5, pos.y()+5); show(); } void DebuggerToolTipWidget::computeToolTipSize() { //calculate the required height and width int columnsWidth = 0; for (int ii = 0; ii < 2; ++ii) { resizeColumnToContents(ii); columnsWidth += sizeHintForColumn(ii); } int rowsHeight = computeToolTipHeight(indexFromItem(topLevelItem(0))); //calculate available height and width QPoint pos(x(), y()); QRect desktopRect = QApplication::desktop()->availableGeometry(pos); int maxWidth = desktopRect.right() - pos.x() - 5 - 5; int maxHeight = desktopRect.bottom() - pos.y() - 5 - 5; if (columnsWidth > maxWidth) rowsHeight += horizontalScrollBar()->height(); //modify scrollbar appropriately if (rowsHeight > maxHeight) { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); rowsHeight = maxHeight; columnsWidth += verticalScrollBar()->width(); setAutoScroll(true); } else { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setAutoScroll(false); } if (columnsWidth > maxWidth) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); columnsWidth = maxWidth; } else { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } setMinimumSize(columnsWidth + 5, rowsHeight); setMaximumSize(columnsWidth + 5, rowsHeight); } int DebuggerToolTipWidget::computeToolTipHeight(const QModelIndex &index) { int s = rowHeight(index); for (int ii = 0; ii < model()->rowCount(index); ++ii) s += computeToolTipHeight(model()->index(ii, 0, index)); return s; } void DebuggerToolTipWidget::mousePressEvent(QMouseEvent * evt) { QPoint position = evt->pos(); QTreeWidgetItem *pItem = itemAt(position); if (pItem) { int depth = getDepth(pItem); if ((position.x() > (depth-1)*indentation()) && (position.x() < ((depth-1)*indentation()+20))) { m_pModifiedByMouse = pItem; m_pExpansionTimer->start(300); } } QTreeWidget::mousePressEvent(evt); } void DebuggerToolTipWidget::mouseMoveEvent(QMouseEvent * evt) { if (isVisible()) m_pExpansionTimer->start(300); QTreeWidget::mouseMoveEvent(evt); } bool DebuggerToolTipWidget::eventFilter(QObject *obj, QEvent *evt) { switch (evt->type()) { case QEvent::KeyRelease: if (static_cast(evt)->key() == Qt::Key_Escape) hideToolTip(); break; case QEvent::ApplicationDeactivate: hideToolTip(); break; case QEvent::MouseMove: { QPoint mousePos = static_cast(evt)->globalPos(); if (!rect().contains(mapFromGlobal(mousePos)) && !parentWidget()->rect().contains(parentWidget()->mapFromGlobal(mousePos))) hideToolTip(); break; } case QEvent::MouseButtonPress: if ( (obj != this) && (obj != this->viewport()) ) hideToolTip(); break; default: break; } return QTreeWidget::eventFilter(obj, evt); } void DebuggerToolTipWidget::wheelEvent(QWheelEvent * e) { if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOn) { e->accept(); return; } QTreeWidget::wheelEvent(e); } void DebuggerToolTipWidget::onTimeout() { QPoint position = mapFromGlobal(QCursor::pos()); QTreeWidgetItem *pItem = itemAt(position); if (pItem) { if (m_pModifiedByMouse != pItem) { int depth = getDepth(pItem); if ((position.x() > (depth-1)*indentation()) && (position.x() < ((depth-1)*indentation()+20))) { if (!pItem->isExpanded()) expandItem(pItem); } m_pModifiedByMouse = NULL; } } } int DebuggerToolTipWidget::getDepth(QTreeWidgetItem *pItem) { int depth = 0; while(pItem != 0) { depth++; pItem = pItem->parent(); } return depth; } QTreeWidgetItem* addChildren(QTreeWidgetItem* pParentItem, int iconIndex) { QTreeWidgetItem *pTreeWidgetItem = new QTreeWidgetItem(pParentItem); pTreeWidgetItem->setIcon(0, QtUtilities::getPixmap(":/images/img_classtree.bmp", iconIndex, 16, true)); return pTreeWidgetItem; } bool populateValues(QTreeWidgetItem *pParentItem, const QString &key, const RBX::Reflection::Variant varValue) { bool result = true; QString textToSet; if(varValue.isType >()) { boost::shared_ptr pInstance = varValue.get >(); textToSet = pInstance->getName().c_str(); for (RBX::Reflection::PropertyIterator iter = pInstance->properties_begin(); iter != pInstance->properties_end(); ++iter) { if (RBX::Reflection::RefPropertyDescriptor::isRefPropertyDescriptor((*iter).getDescriptor())) { const RBX::Reflection::RefPropertyDescriptor* desc = boost::polymorphic_downcast(&(*iter).getDescriptor()); RBX::Reflection::DescribedBase* pBase = desc->getRefValue(pInstance.get()); if (pBase) { RBX::Instance* instance = boost::polymorphic_downcast(pBase); if (instance) { if (!pInstance->contains(instance)) populateValues(addChildren(pParentItem), desc->name.c_str(), RBX::shared_from(instance)); } else { populateValues(addChildren(pParentItem), desc->name.c_str(), std::string("NULL")); } } } else { if ((*iter).getDescriptor().isReadOnly() || (*iter).getDescriptor().isWriteOnly()) continue; if (!(*iter).getDescriptor().canXmlWrite()) continue; if (!(*iter).getDescriptor().canReplicate()) continue; RBX::Reflection::Variant value; (*iter).getDescriptor().getVariant(pInstance.get(), value); populateValues(addChildren(pParentItem), (*iter).getDescriptor().name.c_str(), value); } } } else if (varValue.isType()) { //get x, y textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Vector2 vec2 = varValue.get(); populateValues(addChildren(pParentItem), "X", vec2.x); populateValues(addChildren(pParentItem), "Y", vec2.y); } else if (varValue.isType()) { //get x, y textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Vector2int16 vec2 = varValue.get(); populateValues(addChildren(pParentItem), "X", int(vec2.x)); populateValues(addChildren(pParentItem), "Y", int(vec2.y)); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Vector3 vec3 = varValue.get(); populateValues(addChildren(pParentItem), "X", vec3.x); populateValues(addChildren(pParentItem), "Y", vec3.y); populateValues(addChildren(pParentItem), "Z", vec3.z); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); G3D::Vector3int16 vec3 = varValue.get(); populateValues(addChildren(pParentItem), "X", int(vec3.x)); populateValues(addChildren(pParentItem), "Y", int(vec3.y)); populateValues(addChildren(pParentItem), "Z", int(vec3.z)); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); G3D::CoordinateFrame coordinateFrame = varValue.get(); populateValues(addChildren(pParentItem), "p", RBX::Reflection::Variant(coordinateFrame.translation)); populateValues(addChildren(pParentItem), "lookVector", RBX::Reflection::Variant(coordinateFrame.lookVector())); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Region3 region = varValue.get(); populateValues(addChildren(pParentItem), "CFrame", RBX::Reflection::Variant(region.getCFrame())); populateValues(addChildren(pParentItem), "Size", RBX::Reflection::Variant(region.getSize())); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Rect2D rect2D = varValue.get(); populateValues(addChildren(pParentItem), "Min", RBX::Reflection::Variant(rect2D.x0y0())); populateValues(addChildren(pParentItem), "Max", RBX::Reflection::Variant(rect2D.x1y1())); populateValues(addChildren(pParentItem), "Width", RBX::Reflection::Variant(rect2D.width())); populateValues(addChildren(pParentItem), "Height", RBX::Reflection::Variant(rect2D.height())); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::Region3int16 region = varValue.get(); populateValues(addChildren(pParentItem), "Min", RBX::Reflection::Variant(region.getMinPos())); populateValues(addChildren(pParentItem), "Max", RBX::Reflection::Variant(region.getMaxPos())); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::BrickColor brickColor = varValue.get(); populateValues(addChildren(pParentItem), "Color", RBX::Reflection::Variant(brickColor.color3())); populateValues(addChildren(pParentItem), "Name", RBX::Reflection::Variant(std::string(brickColor.name()))); populateValues(addChildren(pParentItem), "Number", RBX::Reflection::Variant((int)brickColor.number)); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); G3D::Color3 color = varValue.get(); populateValues(addChildren(pParentItem), "r", RBX::Reflection::Variant(color.r)); populateValues(addChildren(pParentItem), "g", RBX::Reflection::Variant(color.g)); populateValues(addChildren(pParentItem), "b", RBX::Reflection::Variant(color.b)); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::RbxRay ray = varValue.get(); populateValues(addChildren(pParentItem), "Origin", RBX::Reflection::Variant(ray.origin())); populateValues(addChildren(pParentItem), "Direction", RBX::Reflection::Variant(ray.direction())); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::UDim dim = varValue.get(); populateValues(addChildren(pParentItem), "Scale", RBX::Reflection::Variant(dim.scale)); populateValues(addChildren(pParentItem), "Offset", RBX::Reflection::Variant(int(dim.offset))); } else if (varValue.isType()) { textToSet = RBX::StringConverter::convertToString(varValue.get()).c_str(); RBX::UDim2 dim2 = varValue.get(); populateValues(addChildren(pParentItem), "X", RBX::Reflection::Variant(dim2.x)); populateValues(addChildren(pParentItem), "Y", RBX::Reflection::Variant(dim2.y)); } else if (varValue.isType >()) { textToSet = "Map"; shared_ptr valMap = varValue.get >(); for (RBX::Reflection::ValueMap::const_iterator iter = valMap->begin(); iter != valMap->end(); ++iter) { populateValues(addChildren(pParentItem), QString("[%1]").arg(iter->first.c_str()), iter->second); } } else if (varValue.isType >()) { textToSet = "Table"; shared_ptr valMap = varValue.get >(); for (RBX::Reflection::ValueTable::const_iterator iter = valMap->begin(); iter != valMap->end(); ++iter) { populateValues(addChildren(pParentItem), QString("[%1]").arg(iter->first.c_str()), iter->second); } } else if (varValue.isType >()) { textToSet = "Array"; shared_ptr valMap = varValue.get >(); RBX::Reflection::ValueArray::const_iterator iter = valMap->begin(); for (int ii=1; iter != valMap->end(); ++iter, ++ii) { populateValues(addChildren(pParentItem), QString("[%1]").arg(ii), *iter); } } else if (varValue.isType >()) { textToSet = "Instances"; shared_ptr valMap = varValue.get >(); RBX::Instances::const_iterator iter = valMap->begin(); for (int ii=0; iter != valMap->end(); ++iter, ++ii) { populateValues(addChildren(pParentItem), QString::number(ii), *iter); } } else if (varValue.isType >()) { textToSet = "Tuple"; shared_ptr valMap = varValue.get >(); RBX::Reflection::ValueArray::const_iterator iter = valMap->values.begin(); for (int ii=0; iter != valMap->values.end(); ++iter, ++ii) { populateValues(addChildren(pParentItem), QString::number(ii), *iter); } } else if (varValue.isType() || varValue.isType()) { textToSet = QString::number(varValue.get()); } else if (varValue.isType()) { textToSet = "function"; pParentItem->setIcon(0, QtUtilities::getPixmap(":/images/img_classtree.bmp", 4, 16, true)); } else { try { textToSet = varValue.get().c_str(); } catch (...) { result = false; } } pParentItem->setText(0, key); pParentItem->setText(1, textToSet); return result; }