/** * UpdateUIManager.cpp * Copyright (c) 2013 ROBLOX Corp. All rights reserved. */ #include "stdafx.h" #include "UpdateUIManager.h" // Qt Headers #include #include #include #include #include #include #include #include #include #include // Roblox Headers #include "rbx/Log.h" #include "rbx/TaskScheduler.h" #include "v8datamodel/DataModel.h" #include "v8datamodel/DebugSettings.h" #include "v8datamodel/MouseCommand.h" #include "v8datamodel/GameBasicSettings.h" // Roblox Studio Headers #include "AuthoringSettings.h" #include "Roblox.h" #include "RobloxContextualHelp.h" #include "RobloxCustomWidgets.h" #include "RobloxDiagnosticsView.h" #include "RobloxDocManager.h" #include "RobloxFindWidget.h" #include "RobloxGameExplorer.h" #include "RobloxMainWindow.h" #include "RobloxPropertyWidget.h" #include "RobloxScriptReview.h" #include "RobloxSettings.h" #include "RobloxTaskScheduler.h" #include "RobloxTextOutputWidget.h" #include "RobloxToolBox.h" #include "RobloxTutorials.h" #include "RobloxTreeWidget.h" #include "CommonInsertWidget.h" #include "RobloxIDEDoc.h" #include "QtUtilities.h" #include "AuthoringSettings.h" #include "ScriptAnalysisWidget.h" #include "RobloxTeamCreateWidget.h" QString UpdateUIManager::defaultCommands = "fileOpenAction fileNewAction fileExitAction aboutRobloxAction actionStartPage actionFullScreen " "onlineHelpAction objectBrowserAction instanceDumpAction settingsAction openPluginsFolderAction " "fastLogDumpAction filePublishedProjectsAction shortcutHelpAction managePluginsAction switchWindowsMenu resetViewAction __qtn_Quick_Access_Button " "customizeQuickAccessAction viewCommandBarAction fileOpenRecentSavesAction "; static QString kActionsThatBypassOnMenuShowStatusUpdates = "publishGameAction"; // Added following actions if we want to support theme modification at runtime // Office2007Blue Office2007Black Office2007Silver Office2007Aqua Windows7Scenic Office2010Blue Office2010Silver Office2010Black Office2013White Office2013Gray bool UpdateUIManager::m_longProcessInProgress = false; FASTFLAG(StudioShowTutorialsByDefault) FASTFLAG(StudioSeparateActionByActivationMethod) UpdateUIManager& UpdateUIManager::Instance() { static UpdateUIManager instance; return instance; } UpdateUIManager::UpdateUIManager() : m_pRobloxMainWindow(NULL) , m_PauseStatusBar(0) , m_isRunning(false) , m_dockRestoreStates(eDW_MAX) , m_BusyState(0) { } UpdateUIManager::~UpdateUIManager() { } void UpdateUIManager::saveDocksGeometry() { RobloxSettings settings; for (int i = 0; i < eDW_MAX; i++) { EDockWindowID id = (EDockWindowID)i; QDockWidget* dock = m_DockWidgets[id]; if (dock->widget()) settings.setValue(dock->objectName() + "/Geometry", dock->widget()->saveGeometry()); } } void UpdateUIManager::loadDocksGeometry() { RobloxSettings settings; for (int i = 0; i < eDW_MAX; i++) { EDockWindowID id = (EDockWindowID)i; QDockWidget* dock = m_DockWidgets[id]; QByteArray state = settings.value(dock->objectName() + "/Geometry").toByteArray(); if(!state.isEmpty() && dock->widget()) dock->widget()->restoreGeometry(state); // force the layout to be updated to fix issues initializing detached on a second monitor dock->layout()->update(); } } QWidget* UpdateUIManager::getExplorerWidget() { return m_ViewWidgets[eDW_OBJECT_EXPLORER]; } QStackedWidget* UpdateUIManager::getChatWidgetStack() { if (m_ChatWidgetStack.isNull()) { // we need to call this function in Main Thread only! RBXASSERT(QThread::currentThread() == qApp->thread()); QDockWidget* dockWidget = new QDockWidget(tr("Chat"), m_pRobloxMainWindow); dockWidget->setObjectName("chatDockWidget"); dockWidget->toggleViewAction()->setIcon(QIcon(":/RibbonBar/images/RibbonIcons/View/Chat.png")); dockWidget->toggleViewAction()->setText(tr("Chat")); dockWidget->toggleViewAction()->setStatusTip(tr("Chat")); dockWidget->toggleViewAction()->setToolTip(tr("View Chat Window")); // add a stack widget with an empty widget (to be shown show during play solo/non cloudedit mode) m_ChatWidgetStack = new QStackedWidget(dockWidget); m_ChatWidgetStack->addWidget(new QWidget); dockWidget->setWidget(m_ChatWidgetStack); configureDockWidget(dockWidget); } return m_ChatWidgetStack; } void UpdateUIManager::initDocks() { // TODO : convert this initialization stuff to a factory m_DockData[eDW_DIAGNOSTICS] = SDockData( UpdateUIManager::getAction("viewDiagnosticsAction"), false ); m_DockData[eDW_TASK_SCHEDULER] = SDockData( UpdateUIManager::getAction("viewTaskSchedulerAction"), false ); m_DockData[eDW_TOOLBOX] = SDockData( UpdateUIManager::getAction("viewToolboxAction"), true ); m_DockData[eDW_TUTORIALS] = SDockData( UpdateUIManager::getAction("viewTutorialsAction"), false, QSize(350, 300)); m_DockData[eDW_BASIC_OBJECTS] = SDockData( UpdateUIManager::getAction("viewBasicObjectsAction"), false ); m_DockData[eDW_SCRIPT_REVIEW] = SDockData( UpdateUIManager::getAction("viewScriptPerformanceAction"), false ); m_DockData[eDW_OBJECT_EXPLORER] = SDockData( UpdateUIManager::getAction("viewObjectExplorerAction"), true, QSize(200, 300)); m_DockData[eDW_PROPERTIES] = SDockData( UpdateUIManager::getAction("viewPropertiesAction"), false, QSize(200, 300)); m_DockData[eDW_OUTPUT] = SDockData( UpdateUIManager::getAction("viewOutputWindowAction"), false); m_DockData[eDW_CONTEXTUAL_HELP] = SDockData( UpdateUIManager::getAction("viewContextualHelpAction"), false); m_DockData[eDW_FIND] = SDockData( UpdateUIManager::getAction("viewFindResultsWindowAction"), false); m_DockData[eDW_GAME_EXPLORER] = SDockData( UpdateUIManager::getAction("gameExplorerAction"), false); m_DockData[eDW_SCRIPT_ANALYSIS] = SDockData( UpdateUIManager::getAction("viewScriptAnalysisAction"), false); m_DockData[eDW_TEAM_CREATE] = SDockData( UpdateUIManager::getAction("viewTeamCreateAction"), false); m_DockWidgets[eDW_DIAGNOSTICS] = m_pRobloxMainWindow->diagnosticsDockWidget; m_DockWidgets[eDW_TASK_SCHEDULER] = m_pRobloxMainWindow->taskSchedulerDockWidget; m_DockWidgets[eDW_TOOLBOX] = m_pRobloxMainWindow->toolBoxDockWidget; m_DockWidgets[eDW_TUTORIALS] = m_pRobloxMainWindow->tutorialsDockWidget; m_DockWidgets[eDW_BASIC_OBJECTS] = m_pRobloxMainWindow->basicObjectsDockWidget; m_DockWidgets[eDW_SCRIPT_REVIEW] = m_pRobloxMainWindow->scriptReviewDockWidget; m_DockWidgets[eDW_OBJECT_EXPLORER] = m_pRobloxMainWindow->objectExplorer; m_DockWidgets[eDW_PROPERTIES] = m_pRobloxMainWindow->propertyBrowser; m_DockWidgets[eDW_OUTPUT] = m_pRobloxMainWindow->outputWindow; m_DockWidgets[eDW_CONTEXTUAL_HELP] = m_pRobloxMainWindow->contextualHelp; m_DockWidgets[eDW_FIND] = m_pRobloxMainWindow->findResultsWindow; m_DockWidgets[eDW_GAME_EXPLORER] = m_pRobloxMainWindow->gameExplorerDockWidget; m_DockWidgets[eDW_SCRIPT_ANALYSIS] = m_pRobloxMainWindow->scriptAnalysisDockWidget; m_DockWidgets[eDW_TEAM_CREATE] = m_pRobloxMainWindow->teamCreateDockWidget; m_ViewWidgets[eDW_DIAGNOSTICS] = new RobloxDiagnosticsView(); m_ViewWidgets[eDW_TASK_SCHEDULER] = new RobloxTaskScheduler(); m_ViewWidgets[eDW_TOOLBOX] = new RobloxToolBox(); m_ViewWidgets[eDW_TUTORIALS] = new RobloxTutorials(); m_ViewWidgets[eDW_BASIC_OBJECTS] = new InsertObjectWidget(m_pRobloxMainWindow->basicObjectsDockWidget); m_ViewWidgets[eDW_SCRIPT_REVIEW] = new RobloxScriptReview(); m_ViewWidgets[eDW_TEAM_CREATE] = new RobloxTeamCreateWidget(m_pRobloxMainWindow->teamCreateDockWidget); m_ViewWidgets[eDW_OBJECT_EXPLORER] = new RobloxExplorerWidget(m_pRobloxMainWindow->objectExplorer); m_ViewWidgets[eDW_PROPERTIES] = new RobloxPropertyWidget(m_pRobloxMainWindow->propertyBrowser); m_ViewWidgets[eDW_OUTPUT] = m_pRobloxMainWindow->getOutputWidget(); m_ViewWidgets[eDW_CONTEXTUAL_HELP] = new RobloxContextualHelp(); m_ViewWidgets[eDW_FIND] = &RobloxFindWidget::singleton(); m_ViewWidgets[eDW_GAME_EXPLORER] = new RobloxGameExplorer(m_pRobloxMainWindow->gameExplorerDockWidget); m_ViewWidgets[eDW_SCRIPT_ANALYSIS] = new ScriptAnalysisWidget(m_pRobloxMainWindow->scriptAnalysisDockWidget); for ( int i = 0 ; i < eDW_MAX ; ++i ) { EDockWindowID id = (EDockWindowID)i; // Unfortunately we need to remove all the dockwidgets and re-add. If we don't we end up with a bug (DE5372) m_pRobloxMainWindow->removeDockWidget(m_DockWidgets[id]); if (id == eDW_BASIC_OBJECTS || id == eDW_DIAGNOSTICS || id == eDW_SCRIPT_REVIEW || id == eDW_TASK_SCHEDULER || id == eDW_TOOLBOX || id == eDW_CONTEXTUAL_HELP || id == eDW_GAME_EXPLORER || id == eDW_TUTORIALS || id == eDW_TEAM_CREATE) { m_pRobloxMainWindow->addDockWidget(Qt::LeftDockWidgetArea, m_DockWidgets[id]); } else if (id == eDW_PROPERTIES || id == eDW_OBJECT_EXPLORER) { m_pRobloxMainWindow->addDockWidget(Qt::RightDockWidgetArea, m_DockWidgets[id]); } else if (id == eDW_OUTPUT || id == eDW_FIND || id == eDW_SCRIPT_ANALYSIS) { m_pRobloxMainWindow->addDockWidget(Qt::BottomDockWidgetArea, m_DockWidgets[id]); } // make sure all the proper data and widgets were created RBXASSERT(m_DockData.contains(id)); RBXASSERT(m_DockWidgets.contains(id)); RBXASSERT(!m_DockWidgets[id]->objectName().isEmpty()); RBXASSERT(m_ViewWidgets.contains(id)); RBXASSERT(!m_DockData[id].UpdateAction->objectName().isEmpty()); // hook up views to docks if (m_ViewWidgets.contains(id)) m_DockWidgets[id]->setWidget(m_ViewWidgets[id]); m_actionDockMap.insert(m_DockData[id].UpdateAction->objectName(), id); // make sure we disconnect the original SLOT QObject::disconnect(m_DockWidgets[id]->toggleViewAction(), SIGNAL(triggered(bool)), m_DockWidgets[id], SLOT(_q_toggleView(bool))); // now connect toggleViewAction of dock widget with UpdateAction QObject::connect(m_DockWidgets[id]->toggleViewAction(), SIGNAL(triggered(bool)), m_DockData[id].UpdateAction, SLOT(trigger())); // Used to handle close event, etc. m_DockWidgets[id]->installEventFilter(this); } } void UpdateUIManager::init(RobloxMainWindow *pMainWindow) { if (!pMainWindow) return; m_pRobloxMainWindow = pMainWindow; #ifdef _WIN32 if (!pMainWindow->isRibbonStyle()) // This fails for ribbon for some reason qApp->setStyleSheet("QScrollBar { scrollbar-contextmenu: 0}");//remove scrollbar contextmenu from our application (this doesn't work for QWebView related scrollbars) #endif initDocks(); setupStatusBar(); setupSlots(); // Init our axisWidgets and 3d grid pMainWindow->toggleAxisWidgetAction->setChecked(get3DAxisEnabled()); pMainWindow->toggle3DGridAction->setChecked(get3DGridEnabled()); m_isRunning = true; #if WINVER >= 0x601 // initialize Vista/Win7 task bar interface m_isTaskBarInitialized = false; m_isIndeterminateProgress = false; m_taskbar = NULL; HRESULT hr = ::CoCreateInstance( CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_taskbar) ); if ( hr == S_OK ) { hr = m_taskbar->HrInit(); if ( hr == S_OK ) { m_isTaskBarInitialized = true; } else { m_taskbar->Release(); m_taskbar = NULL; } } #endif //update status bar QTimer::singleShot(0, this, SLOT(updateStatusBar())); } void UpdateUIManager::setupStatusBar() { //don't create border for the permanent widgets QStatusBar *pStatusBar = m_pRobloxMainWindow->statusBar(); if (!pStatusBar) return; pStatusBar->setStyleSheet("QStatusBar::item { border: none; } "); //create labels to show different messages m_pTaskSchedulerSBLabel = new QLabel; m_pTimeSBLabel = new QLabel; m_pCPUSBLabel = new QLabel; m_pRAMSBLabel = new QLabel; m_pFPSSBLabel = new QLabel; m_pTaskSchedulerSBLabel->setMargin(5); m_pTimeSBLabel->setMargin(5); m_pCPUSBLabel->setMargin(5); m_pRAMSBLabel->setMargin(5); m_pFPSSBLabel->setMargin(5); //add labels to statusbar pStatusBar->addPermanentWidget(m_pTaskSchedulerSBLabel); pStatusBar->addPermanentWidget(m_pTimeSBLabel); pStatusBar->addPermanentWidget(m_pFPSSBLabel); pStatusBar->addPermanentWidget(m_pCPUSBLabel); pStatusBar->addPermanentWidget(m_pRAMSBLabel); } void UpdateUIManager::setMenubarEnabled(bool state) { QList childMenus = m_pRobloxMainWindow->findChildren(); for (int jj=0; jj < childMenus.size(); ++jj) { if ( state ) connect(childMenus.at(jj), SIGNAL(aboutToShow()), this, SLOT(onMenuShow())); else disconnect(childMenus.at(jj), SIGNAL(aboutToShow()), this, SLOT(onMenuShow())); } } void UpdateUIManager::setupSlots() { QList childMenus = m_pRobloxMainWindow->findChildren(); for ( int j = 0; j < childMenus.size() ; ++j ) connect(childMenus.at(j),SIGNAL(aboutToShow()),this,SLOT(onMenuShow())); connect(&m_pRobloxMainWindow->fillColorToolButton(),SIGNAL(frameShown()), this,SLOT(onMenuShow())); connect(&m_pRobloxMainWindow->materialToolButton(),SIGNAL(frameShown()), this,SLOT(onMenuShow())); } QString UpdateUIManager::getDockGeometryKeyName(EDockWindowID dockId) const { return m_DockWidgets[dockId]->objectName() + "_geometry"; } void UpdateUIManager::setDockVisibility(EDockWindowID dockId, bool visible) { if (m_ViewWidgets.contains(dockId)) m_ViewWidgets[dockId]->setVisible(visible); m_DockWidgets[dockId]->setVisible(visible); } /** * Sets up the view menu with the dock widget actions. * * @param insertBefore location to add the actions in the menu */ void UpdateUIManager::setupDockWidgetsViewMenu(QAction& insertBefore) { // Connect all dock widgets to their appropriate actions and add to the view menu for ( int i = 0 ; i < eDW_MAX ; ++i ) { EDockWindowID id = (EDockWindowID)i; QAction& action = *m_DockData[id].UpdateAction; // TODO : show view icons in menu action.setIconVisibleInMenu(false); m_pRobloxMainWindow->viewToolsToolBar->addAction(&action); m_pRobloxMainWindow->menuView->insertAction(&insertBefore,&action); } } void UpdateUIManager::shutDown() { m_isRunning = false; #if WINVER >= 0x601 // delete Vista/Win7 task bar interface if ( m_isTaskBarInitialized ) m_taskbar->Release(); #endif } void UpdateUIManager::onMenuShow() { IRobloxDoc *pDoc = RobloxDocManager::Instance().getCurrentDoc(); if (!pDoc) return; const QObject *pSender = sender(); if (!pSender) return; bool isTreeWidgetConextMenu = (pSender->parent() == m_pRobloxMainWindow->treeWidgetStack().currentWidget()); IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc(); if (pSender->inherits("QMenu")) { QList menuActions = ((const QMenu *)pSender)->actions(); QAction *pAction = NULL; bool checkedState = false, enabledState = false; for (int i = 0; i < menuActions.size(); ++i) { pAction = menuActions.at(i); if (!pAction) continue; QString objectName = pAction->objectName(); if (objectName.isEmpty()) continue; if (kActionsThatBypassOnMenuShowStatusUpdates.contains(objectName)) continue; if (defaultCommands.contains(objectName)) { pAction->setEnabled(true); } else if (isTreeWidgetConextMenu && playDoc && playDoc->actionState(objectName, enabledState, checkedState)) { pAction->setEnabled(enabledState); pAction->setChecked(checkedState); } else if (pDoc->actionState(objectName, enabledState, checkedState)) { pAction->setEnabled(enabledState); pAction->setChecked(checkedState); } } } if ( isTreeWidgetConextMenu ) pDoc->handleAction("actionCustomContextMenuShow"); } void UpdateUIManager::onMenuHide() { IRobloxDoc *pDoc = RobloxDocManager::Instance().getCurrentDoc(); if (!pDoc) return; const QObject *pSender = sender(); if(!pSender) return; if(!pSender->inherits("QMenu")) return; if (pSender->parent() == m_pRobloxMainWindow->treeWidgetStack().currentWidget()) pDoc->handleAction("actionCustomContextMenuHide"); } void UpdateUIManager::setDefaultApplicationState() { QList allActions = m_pRobloxMainWindow->findChildren(); QAction *pAction = NULL; for (int i = 0; i < allActions.size(); ++i) { pAction = allActions.at(i); if (!pAction || pAction->objectName().isEmpty() || defaultCommands.contains(pAction->objectName()) || pAction->objectName() == "executeScriptAction" || getDockActionNames().contains(pAction->objectName())) // docks are always enabled continue; pAction->setEnabled(false); pAction->setChecked(false); } } void UpdateUIManager::updateToolBars() { onToolBarsUpdate(); } void UpdateUIManager::onToolBarsUpdate() { updateToolbarTask(); } void UpdateUIManager::updateToolbarTask() { // update actions with shortcut, these need to be updated in both ribbon and menu updateAction(*m_pRobloxMainWindow->deleteSelectedAction); updateAction(*m_pRobloxMainWindow->selectAllAction); updateAction(*m_pRobloxMainWindow->screenShotAction); updateAction(*m_pRobloxMainWindow->pasteAction); updateAction(*m_pRobloxMainWindow->pasteIntoAction); updateAction(*m_pRobloxMainWindow->playSoloAction); updateAction(*m_pRobloxMainWindow->startServerAction); updateAction(*m_pRobloxMainWindow->startPlayerAction); updateAction(*m_pRobloxMainWindow->findAction); updateAction(*m_pRobloxMainWindow->replaceAction); updateAction(*m_pRobloxMainWindow->findNextAction); updateAction(*m_pRobloxMainWindow->findPreviousAction); updateAction(*m_pRobloxMainWindow->goToLineAction); updateAction(*m_pRobloxMainWindow->simulationPlayAction); updateAction(*m_pRobloxMainWindow->simulationRunAction); updateAction(*m_pRobloxMainWindow->simulationResetAction); updateAction(*m_pRobloxMainWindow->simulationStopAction); updateAction(*m_pRobloxMainWindow->rotateSelectionAction); updateAction(*m_pRobloxMainWindow->zoomExtentsAction); if (!m_pRobloxMainWindow->isRibbonStyle()) { updateActions(m_pRobloxMainWindow->editCameraToolBar->actions()); updateActions(m_pRobloxMainWindow->runToolBar->actions()); updateActions(m_pRobloxMainWindow->advToolsToolBar->actions()); updateActions(m_pRobloxMainWindow->commandToolBar->actions()); updateActions(m_pRobloxMainWindow->menuTools->actions()); updateActions(m_pRobloxMainWindow->scriptMenu->actions()); updateActions(m_pRobloxMainWindow->standardToolBar->actions()); } else { // Loop through every ribbon page, and group, and update all actions const RibbonMainWindow* pMainWindow = static_cast(m_pRobloxMainWindow); const int pagesCount = pMainWindow->ribbonBar()->getPageCount(); for (int i = 0; i < pagesCount; i++) { const QList ribbonGroups = pMainWindow->ribbonBar()->getPage(i)->findChildren(); for (int k = 0; k < ribbonGroups.count(); k++) { RibbonGroup* group = ribbonGroups[k]; const QList galleries = group->findChildren(); for (int j = 0; j < galleries.count(); j++) { const bool galleryEnabled = RobloxDocManager::Instance().getPlayDoc() != NULL; galleries[j]->setEnabled(galleryEnabled); } updateActions(group->actions()); if (group->getOptionButtonAction() && group->getOptionButtonAction()->isVisible()) updateAction(*group->getOptionButtonAction()); } } // Update quickaccess bar's actions if (pMainWindow->ribbonBar()->getQuickAccessBar()) updateActions(pMainWindow->ribbonBar()->getQuickAccessBar()->actions()); // Update all button groups, since they are not associated with an action QList buttonGroups = pMainWindow->findChildren(); for (int ii=0; ii < buttonGroups.count(); ++ii) modifyButtonGroupState(*buttonGroups.at(ii)); // Update all menu actions under ToolButtonProxyMenu QList toolButtonProxies = pMainWindow->findChildren(); for (int ii=0; ii < toolButtonProxies.count(); ++ii) updateActions(toolButtonProxies.at(ii)->actions()); updateAction(*m_pRobloxMainWindow->anchorAction); // Updated based on selection } } void UpdateUIManager::updateActions(const QList& actions) { for (int i = 0; i < actions.size(); ++i) { QAction* action = actions.at(i); updateAction(*action); if ( action->inherits("QWidgetAction") ) { QWidgetAction* widgetAction = static_cast(action); QWidget* pDefaultWidget = widgetAction->defaultWidget(); if ( !pDefaultWidget ) continue; if ( pDefaultWidget->inherits("QToolButton") ) { QToolButton* button = static_cast(pDefaultWidget); updateAction(*button->defaultAction()); } else if ( pDefaultWidget->inherits("QRadioButton") ) { QRadioButton* button = static_cast(pDefaultWidget); // Block all signals: Temporary Hack to fix Ribbon Bar slow down caused by the Radio buttons. bool oldState = pDefaultWidget->blockSignals(true); const QString buttonName = button->objectName(); QAction* action = m_pRobloxMainWindow->findChild(buttonName); if (action) { updateAction(*action); if (FFlag::StudioSeparateActionByActivationMethod) { button->setChecked(action->isChecked()); button->setEnabled(action->isEnabled()); } } // Unblock all signals: Temporary hack to fix Ribbon Bar slow down caused by Radio buttons. pDefaultWidget->blockSignals(oldState); } } } } void UpdateUIManager::updateAction(QAction& action) { if (action.objectName().isEmpty() ) return; bool checkedState = action.isChecked(); bool enabledState = action.isEnabled(); // These commands should always be enabled if ( UpdateUIManager::defaultCommands.contains(action.objectName()) ) { modifyActionState(action,true,checkedState); } else { IRobloxDoc* pDoc = RobloxDocManager::Instance().getCurrentDoc(); if ( !pDoc ) { // No current doc, disabled, but keep current checkedstate modifyActionState(action, false, checkedState); } else if (pDoc->actionState(action.objectName(), enabledState, checkedState)) { // pass on to current doc modifyActionState(action, enabledState, checkedState); } } } void UpdateUIManager::modifyActionState(QAction& action,bool enabledState,bool checkedState) { // force disabled if we're in a busy state if ( UpdateUIManager::Instance().isBusy() ) enabledState = false; m_pRobloxMainWindow->updateInternalWidgetsState(&action, enabledState, checkedState); // don't spam if we're already configured properly if ( action.isEnabled() == enabledState && (!action.isCheckable() || action.isChecked() == checkedState) ) { return; } if (QThread::currentThread() == thread()) { action.setEnabled(enabledState); action.setChecked(checkedState); } else { QMetaObject::invokeMethod(&action,"setEnabled",Qt::QueuedConnection,Q_ARG(bool,enabledState)); QMetaObject::invokeMethod(&action,"setChecked",Qt::QueuedConnection,Q_ARG(bool,checkedState)); } } void UpdateUIManager::modifyButtonGroupState(QButtonGroup &pButtonGroup) { bool checkedState = false; bool enabledState = false; IRobloxDoc* pDoc = RobloxDocManager::Instance().getCurrentDoc(); if (pDoc) pDoc->actionState(pButtonGroup.objectName(), enabledState, checkedState); QList buttons = pButtonGroup.buttons(); for (int ii=0; ii < buttons.count(); ++ii) { buttons.at(ii)->setEnabled(enabledState); if (buttons.at(ii)->isCheckable()) { if (QThread::currentThread() == thread()) { buttons.at(ii)->setChecked(checkedState); } else { QMetaObject::invokeMethod(buttons.at(ii),"setChecked",Qt::QueuedConnection,Q_ARG(bool,checkedState)); } } } } bool UpdateUIManager::toggleDockView(const QString& actionName) { QMap::iterator iter = m_actionDockMap.find(actionName); if (iter == m_actionDockMap.end()) return false; EDockWindowID id = iter.value(); if ( m_DockWidgets[id]->widget() != m_ViewWidgets[id] ) { RBXASSERT(0); m_DockWidgets[id]->setWidget(m_ViewWidgets[id]); } bool visibility1 = m_DockWidgets[id]->isVisible(); bool wasSetChecked = m_DockData[id].UpdateAction->isChecked(); setDockVisibility(id, wasSetChecked); bool visibility2 = m_DockWidgets[id]->isVisible(); if (wasSetChecked) m_DockWidgets[id]->raise(); if (visibility1 == visibility2) return false; return true; } /** * Stops status bar updates. * Increments pause state. Paused if pause state > 0. */ void UpdateUIManager::pauseStatusBarUpdate() { ++m_PauseStatusBar; } #if WINVER >= 0x601 /** * Synchronizes the task bar progress with the progress bar progress. */ void UpdateUIManager::configureTaskBar(QProgressBar* progressBar) { if ( !m_isTaskBarInitialized ) return; m_taskProgressBar = progressBar; if ( !progressBar ) { m_taskbar->SetProgressState(m_pRobloxMainWindow->winId(),TBPF_NOPROGRESS); return; } int min = progressBar->minimum(); int max = progressBar->maximum(); if ( min == max ) // indeterminate { m_isIndeterminateProgress = true; m_taskbar->SetProgressState(m_pRobloxMainWindow->winId(),TBPF_INDETERMINATE); } else { m_isIndeterminateProgress = false; m_taskbar->SetProgressState(m_pRobloxMainWindow->winId(),TBPF_NORMAL); m_totalProgress = max - min; // convert to 0 to max m_taskbar->SetProgressValue(m_pRobloxMainWindow->winId(),0,m_totalProgress); } } #endif // WINVER /** * Change the cursor to the wait cursor and shut off some operations while busy. * Only marked unbusy if busy state is 0. Each setBusy(true) must be followed by a * setBusy(false) so the busy state will reach 0. */ void UpdateUIManager::setBusy(bool busy, bool useWaitCursor) { m_BusyState += busy ? 1 : -1; m_pRobloxMainWindow->setUpdatesEnabled(false); if ( m_BusyState == 1 && busy ) { // only go into busy state 1 for the first time. // if we go to 2+ then back to 1, we don't want to redo this code if (useWaitCursor) QApplication::setOverrideCursor(Qt::WaitCursor); pauseStatusBarUpdate(); //save currently focused widget m_BusyFocusWidget = QApplication::focusWidget(); // disable any menu updates UpdateUIManager::Instance().setMenubarEnabled(false); // disable widgets m_pRobloxMainWindow->enableScriptCommandInput(false); m_pRobloxMainWindow->centralWidget()->setEnabled(false); // disable all actions m_BusyEnabledActions.clear(); QList actions = m_pRobloxMainWindow->findChildren(); QList::iterator iter = actions.begin(); while ( iter != actions.end() ) { QAction* action = *iter; // only worry about actions with names if ( !action->objectName().isEmpty() ) { m_BusyEnabledActions[action] = action->isEnabled(); action->setEnabled(false); } ++iter; } // disable the dock widgets for ( int i = 0 ; i < eDW_MAX ; ++i ) { const EDockWindowID id = (EDockWindowID)i; m_DockWidgets[id]->setEnabled(false); } #if WINVER >= 0x601 // change task bar overlay icon to a busy icon if ( m_isTaskBarInitialized ) { QIcon icon = m_pRobloxMainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning); HICON overlay_icon = icon.isNull() ? NULL : icon.pixmap(48).toWinHICON(); m_taskbar->SetOverlayIcon(m_pRobloxMainWindow->winId(),overlay_icon,L"Busy"); if ( overlay_icon ) ::DestroyIcon(overlay_icon); } #endif } else if ( m_BusyState == 0 ) { if (useWaitCursor) QApplication::restoreOverrideCursor(); resumeStatusBarUpdate(); // enable menu updates UpdateUIManager::Instance().setMenubarEnabled(true); // enable widgets m_pRobloxMainWindow->enableScriptCommandInput(true); m_pRobloxMainWindow->centralWidget()->setEnabled(true); // restore focus if we can, otherwise set to main window // this fixes some focus issues on mac m_pRobloxMainWindow->centralWidget()->setFocus(); if ( !m_BusyFocusWidget.isNull() ) m_BusyFocusWidget->setFocus(); else if ( QApplication::focusWidget() ) QApplication::focusWidget()->setFocus(); else m_pRobloxMainWindow->setFocus(); m_BusyFocusWidget = NULL; // enable all main actions QList actions = m_BusyEnabledActions.keys(); QList::iterator iter = actions.begin(); while ( iter != actions.end() ) { QAction* action = *iter; action->setEnabled(m_BusyEnabledActions[action]); ++iter; } m_BusyEnabledActions.clear(); // enable the dock widgets for ( int i = 0 ; i < eDW_MAX ; ++i ) { const EDockWindowID id = (EDockWindowID)i; m_DockWidgets[id]->setEnabled(true); } #if WINVER >= 0x601 // restore task bar overlay icon if ( m_isTaskBarInitialized ) m_taskbar->SetOverlayIcon(m_pRobloxMainWindow->winId(),NULL,L""); #endif } m_pRobloxMainWindow->setUpdatesEnabled(true); } /** * Resumes the status bar updates. * The pause state is decremented. Only at 0 will the status bar be updated. */ void UpdateUIManager::resumeStatusBarUpdate() { --m_PauseStatusBar; if (m_isRunning && !m_PauseStatusBar) QTimer::singleShot(200, this, SLOT(updateStatusBar())); } /** * Creates a new progress bar and adds it to the status bar. * If min == max then the progress bar is set to indeterminate. * Synchronizes the task bar progress. * * @param message text message displayed in a label to the left * @param min minimum progress bar value (start) * @param max maximum progress bar value (end) */ QProgressBar* UpdateUIManager::showProgress(const QString& message,int min,int max) { QStatusBar* statusBar = m_pRobloxMainWindow->statusBar(); // configure progress bar widget QProgressBar* progressBar = new QProgressBar(statusBar); progressBar->setTextVisible(true); progressBar->setMinimum(min); progressBar->setMaximum(max); progressBar->setValue(min); // configure static label QLabel* label = new QLabel(message,progressBar); statusBar->setUpdatesEnabled(false); statusBar->clearMessage(); statusBar->addWidget(label,0); statusBar->addWidget(progressBar,1); statusBar->setUpdatesEnabled(true); // set up indeterminate if necessary if ( min == max ) progressBar->setValue(-1); m_ProgressBars.append(progressBar); m_ProgressLabels.append(label); // make sure they get rendered at least once progressBar->show(); label->show(); #if WINVER >= 0x601 // sync task bar progress with progress bar configureTaskBar(progressBar); #endif return progressBar; } /** * Deletes the progress bar. */ void UpdateUIManager::hideProgress(QProgressBar* progressBar) { RBXASSERT(m_ProgressBars.contains(progressBar)); int index = m_ProgressBars.indexOf(progressBar); if ( index != -1 ) { delete m_ProgressBars[index]; m_ProgressBars.remove(index); delete m_ProgressLabels[index]; m_ProgressLabels.remove(index); } #if WINVER >= 0x601 if ( m_isTaskBarInitialized ) { // reset the "main" progress bar used to synchronize the task bar if ( progressBar == m_taskProgressBar ) { configureTaskBar(NULL); if ( !m_ProgressBars.empty() ) configureTaskBar(m_ProgressBars.back()); } } #endif } /** * Updates the progress bar with a new value. * If the value is -1 and the progress bar is indeterminate, update * the indeterminate value. * Synchronizes the task bar if this progress bar is the "main" one. */ void UpdateUIManager::updateProgress(QProgressBar* progressBar,int value) { RBXASSERT(progressBar); // check for indeterminate and update if ( value == -1 ) value = progressBar->value() + 1; progressBar->setValue(value); progressBar->update(); QApplication::processEvents(); #if WINVER >= 0x601 if ( progressBar == m_taskProgressBar ) { // synchronize the task bar progress if ( m_isTaskBarInitialized && !m_isIndeterminateProgress ) m_taskbar->SetProgressValue(m_pRobloxMainWindow->winId(),value,m_totalProgress); } #endif } /** * "Blocks" the main thread while a child thread is processed. * The main thread can still handles events and GUI updates. This allows it to be * responsive and update the progress bar. The application can set the state * as "busy" to prevent reentrancy. Long running processes general lock resources and * reentrancy will cause a deadlock. * * @param message message to display for the progress bar * @param userWorkFunc long running function to run in a child thread */ void UpdateUIManager::waitForLongProcess( const QString& message, boost::function userWorkFunc ) { QEventLoop eventLoop; QMutex eventLoopMutex; volatile bool done = false; m_longProcessInProgress = true; new boost::thread( boost::bind( &UpdateUIManager::exectueProgressFunction, this, userWorkFunc, &eventLoop, &eventLoopMutex, (bool*)&done ) ); // wait for approx 300 ms, check if done and abort // this avoids needing to show the progress and setting busy if the operation is fast for ( int i = 0 ; i < 30 && !done ; i++ ) QtUtilities::sleep(10); if ( !done ) { setBusy(true); QProgressBar* progressBar = showProgress(message); // if still not done, then start the event loop if ( !done ) { QMutexLocker locker(&eventLoopMutex); if ( !done ) eventLoop.exec(); } hideProgress(progressBar); setBusy(false); } m_longProcessInProgress = false; } /** * Thread main for a progress function. * Unblocks the main thread when done. */ void UpdateUIManager::exectueProgressFunction( boost::function userWorkFunc, QEventLoop* eventLoop, QMutex* eventLoopMutex, bool* done ) { RBXASSERT(eventLoop); RBXASSERT(eventLoopMutex); RBXASSERT(done); try { userWorkFunc(); } catch(...) { RBXASSERT(false); } // check to see if the event loop is running if ( eventLoopMutex->tryLock() ) { // abort starting the event loop completely *done = true; eventLoopMutex->unlock(); } else { // we're already executing or starting to execute, can't set done now // wait until we really are executing while ( !eventLoop->isRunning() ) QtUtilities::sleep(10); // now shut it down since we know its running eventLoop->exit(); } } void UpdateUIManager::commonContextMenuActions(QList &commonActions, bool insertIntoPasteMode) { commonActions.append(m_pRobloxMainWindow->cutAction); commonActions.append(m_pRobloxMainWindow->copyAction); commonActions.append(insertIntoPasteMode ? m_pRobloxMainWindow->pasteIntoAction : m_pRobloxMainWindow->pasteAction); commonActions.append(m_pRobloxMainWindow->duplicateSelectionAction); commonActions.append(m_pRobloxMainWindow->deleteSelectedAction); commonActions.append(m_pRobloxMainWindow->renameObjectAction); commonActions.append(NULL); commonActions.append(m_pRobloxMainWindow->groupSelectionAction); commonActions.append(m_pRobloxMainWindow->ungroupSelectionAction); commonActions.append(m_pRobloxMainWindow->selectChildrenAction); commonActions.append(m_pRobloxMainWindow->zoomExtentsAction); commonActions.append(NULL); commonActions.append(m_pRobloxMainWindow->insertIntoFileAction); commonActions.append(NULL); commonActions.append(m_pRobloxMainWindow->selectionSaveToFileAction); commonActions.append(m_pRobloxMainWindow->saveToRobloxAction); commonActions.append(m_pRobloxMainWindow->createNewLinkedSourceAction); commonActions.append(m_pRobloxMainWindow->publishAsPluginAction); commonActions.append(m_pRobloxMainWindow->exportSelectionAction); } void UpdateUIManager::updateStatusBar() { if (!AuthoringSettings::singleton().diagnosticsBarEnabled) { QString empty; m_pTaskSchedulerSBLabel->setText(empty); m_pCPUSBLabel->setText(empty); m_pRAMSBLabel->setText(empty); m_pTimeSBLabel->setText(empty); m_pFPSSBLabel->setText(empty); return; } const RBX::TaskScheduler& ts(RBX::TaskScheduler::singleton()); QString str; str.sprintf( "Sleep: %.1f Wait: %.1f Run: %.2f Affinity: %d%% Scheduler: %.0f/s %d%% SortFreq: %.0f/s ErrorCalc: %.0f/s", ts.numSleepingJobs(), ts.numWaitingJobs(), ts.numRunningJobs(), (int)(100.0 * ts.threadAffinity()), ts.schedulerRate(), (int)(100.0 * ts.getSchedulerDutyCyclePerThread()), ts.getSortFrequency(), ts.getErrorCalculationRate()); m_pTaskSchedulerSBLabel->setText(str); // CPU label double cores = RBX::DebugSettings::singleton().processCores(); QString coresStr; coresStr.sprintf("Cores: %.1g", cores); m_pCPUSBLabel->setText(coresStr); // RAM Label int mem = RBX::DebugSettings::singleton().privateWorkingSetBytes(); m_pRAMSBLabel->setText(RBX::Log::formatMem(mem).c_str()); IRobloxDoc *pDoc = RobloxDocManager::Instance().getCurrentDoc(); if (pDoc) { pDoc->handleAction("actionUpdateStatusBar"); } if (m_isRunning && !m_PauseStatusBar) QTimer::singleShot(200, this, SLOT(updateStatusBar())); } void UpdateUIManager::setStatusLabels(QString sTimeLabel, QString sFpsLabel) { m_pTimeSBLabel->setText(sTimeLabel); m_pFPSSBLabel->setText(sFpsLabel); } void UpdateUIManager::onQuickInsertFocus() { RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc(); if (!playDoc) return; getViewWidget(eDW_BASIC_OBJECTS).startingQuickInsert(QApplication::focusWidget(), !m_DockWidgets[eDW_BASIC_OBJECTS]->isVisible()); setDockVisibility(eDW_BASIC_OBJECTS, true); m_DockWidgets[eDW_BASIC_OBJECTS]->raise(); m_ViewWidgets[eDW_BASIC_OBJECTS]->activateWindow(); m_ViewWidgets[eDW_BASIC_OBJECTS]->setFocus(Qt::ShortcutFocusReason); } void UpdateUIManager::filterExplorer() { m_ViewWidgets[eDW_OBJECT_EXPLORER]->setFocus(Qt::ShortcutFocusReason); } bool UpdateUIManager::eventFilter(QObject* watched, QEvent* evt) { // Capture dock widget closing and set checked to false if((evt->type() == QEvent::Show || evt->type() == QEvent::Hide) && watched->inherits("QDockWidget")) { QString dockName = watched->objectName(); for (int i = 0; i < eDW_MAX; i++) { EDockWindowID id = (EDockWindowID)i; if (m_DockWidgets[id]->objectName() == dockName) m_DockData[id].UpdateAction->setChecked(m_DockWidgets[id]->isVisible()); } } return false; } QAction* UpdateUIManager::getAction(const QString &actionName) { QList allActions = m_pRobloxMainWindow->findChildren(actionName); if (allActions.size()) return allActions.at(0); return NULL; } QList UpdateUIManager::getDockActionNames() const { return m_actionDockMap.keys(); } QAction* UpdateUIManager::getDockAction(const EDockWindowID id) const { return m_DockData[id].UpdateAction; } void UpdateUIManager::updateBuildMode() { eBuildMode buildMode = m_pRobloxMainWindow->getBuildMode(); m_pRobloxMainWindow->setUpdatesEnabled(false); RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc(); if ( playDoc ) { playDoc->enableUndo(buildMode != BM_BASIC); if ( buildMode == BM_BASIC ) RobloxDocManager::Instance().setPlayDocCentralWidget(); else RobloxDocManager::Instance().restoreCentralWidget(); } if ( buildMode == BM_BASIC ) { // hide the dock widgets for ( int i = 0 ; i < eDW_MAX ; ++i ) { const EDockWindowID id = (EDockWindowID)i; setDockVisibility(id,false); } // hide the toolbars QObjectList objects = m_pRobloxMainWindow->children(); QObjectList::iterator iter = objects.begin(); for ( ; iter != objects.end() ; ++iter ) { QToolBar* toolbar = dynamic_cast(*iter); if ( toolbar ) toolbar->setVisible(false); } // hide menu and status m_pRobloxMainWindow->menuBar()->setVisible(false); m_pRobloxMainWindow->statusBar()->setVisible(false); m_pRobloxMainWindow->setWindowState(Qt::WindowFullScreen); } else if ( buildMode == BM_ADVANCED ) { // show menu and status if (!m_pRobloxMainWindow->isRibbonStyle()) { m_pRobloxMainWindow->menuBar()->setVisible(true); m_pRobloxMainWindow->statusBar()->setVisible(true); } // reload layout - this will restore all docks and toolbars to previous state //m_pRobloxMainWindow->loadApplicationStates(); //m_pRobloxMainWindow->show(); // show the dock view widgets for ( int i = 0 ; i < eDW_MAX ; ++i ) { const EDockWindowID id = (EDockWindowID)i; if ( m_DockWidgets[id]->isVisible() && m_ViewWidgets[id]) m_ViewWidgets[id]->setVisible(true); } } updateDockActionsCheckedStates(); m_pRobloxMainWindow->updateWindowTitle(); RBX::MouseCommand::enableAdvArrowTool(buildMode != BM_BASIC); RBX::GameBasicSettings::singleton().setStudioMode(buildMode != BM_BASIC); RBX::GameBasicSettings::singleton().setFullScreen(buildMode == BM_BASIC); m_pRobloxMainWindow->setUpdatesEnabled(true); } void UpdateUIManager::updateDockActionsCheckedStates() { // Sets the actions to be checked if the dock widget is opened. for ( int i = 0 ; i < eDW_MAX ; ++i ) { EDockWindowID id = (EDockWindowID)i; m_DockData[id].UpdateAction->setChecked(m_DockWidgets[id]->isVisible()); } } bool UpdateUIManager::get3DAxisEnabled() { RobloxSettings settings; return settings.value("View_AxisWidget", false).toBool(); } void UpdateUIManager::set3DAxisEnabled( bool value ) { RobloxSettings settings; return settings.setValue("View_AxisWidget", value); } bool UpdateUIManager::get3DGridEnabled() { RobloxSettings settings; return settings.value("View_ZeroPlaneGrid", false).toBool(); } void UpdateUIManager::set3DGridEnabled( bool value ) { RobloxSettings settings; return settings.setValue("View_ZeroPlaneGrid", value); } QDockWidget* UpdateUIManager::getDockWidget( EDockWindowID dockId ) { return m_DockWidgets[dockId]; } bool UpdateUIManager::isLongProcessInProgress() { return m_longProcessInProgress; } void UpdateUIManager::showErrorMessage(QString title, QString message) { QMessageBox::critical(&getMainWindow(), title, message); } void UpdateUIManager::configureDockWidget(QDockWidget* dockWidget) { if(!m_pRobloxMainWindow->restoreDockWidget(dockWidget)) m_pRobloxMainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget); if (!m_pRobloxMainWindow->isRibbonStyle()) { m_pRobloxMainWindow->viewToolsToolBar->addAction(dockWidget->toggleViewAction()); } else { if (RibbonPage* pPage = m_pRobloxMainWindow->ribbonBar()->findChild("View")) { if (RibbonGroup* pGroup = pPage->findChild("Show")) pGroup->addAction(dockWidget->toggleViewAction(), Qt::ToolButtonTextBesideIcon); } } dockWidget->toggleViewAction()->setIconVisibleInMenu(false); QObject::disconnect(dockWidget->toggleViewAction(), SIGNAL(triggered(bool)), dockWidget, SLOT(_q_toggleView(bool))); QObject::connect(dockWidget->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(updateActionWidgetVisibility())); } void UpdateUIManager::updateActionWidgetVisibility() { QAction* pAction = qobject_cast(sender()); if (!pAction || !pAction->parentWidget()) return; pAction->parentWidget()->setVisible(pAction->isChecked()); if (pAction->isChecked()) pAction->parentWidget()->raise(); }