/** * RibbonMainWindow.cpp * Copyright (c) 2013 ROBLOX Corp. All rights reserved. */ #include "stdafx.h" #include "RobloxRibbonMainWindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // Roblox Studio includes #include "RobloxSettings.h" #include "RobloxMainWindow.h" #include "RobloxDocManager.h" #include "RobloxIDEDoc.h" #include "RobloxCustomWidgets.h" #include "GalleryItemColor.h" #include "NameValueStoreManager.h" #include "CommonInsertWidget.h" #include "UpdateUIManager.h" #include "AuthoringSettings.h" #include "RobloxToolBox.h" #include "QtUtilities.h" #include "RobloxUser.h" #include "PluginAction.h" #include "RobloxCustomWidgets.h" #include "RobloxQuickAccessConfig.h" #include "StudioDeviceEmulator.h" FASTFLAGVARIABLE(SurfaceLightEnabled, false) FASTFLAG(StudioSeparateActionByActivationMethod) //static const char* newLayoutXmlConfigFile = "RobloxStudioRibbonRelease.xml"; static const char* updatedLayoutXmlConfigFile = "RobloxStudioRibbon.xml"; static const char* elemMainWindow = "MainWindow"; static const char* elemQuickAccessBar = "QuickAccessBar"; static const char* elemeSystemButton = "SystemButton"; static const char* elemSystemPopup = "SystemPopup"; static const char* elemRibbonBar = "RibbonBar"; static const char* elemTabPage = "tabpage"; static const char* elemGroup = "group"; static const char* elemAddAction = "addaction"; static const char* elemAddRadioButton = "addradiobutton"; static const char* elemAddCheckBox = "addcheckbox"; static const char* elemAddComboBox = "addcombobox"; static const char* elemAddSeparator = "addseparator"; static const char* elemActionGroup = "actiongroup"; static const char* elemSubMenu = "submenu"; static const char* elemMenuButton = "menubutton"; static const char* elemMenuPopupButton = "menupopupbutton"; static const char* elemColorPicker = "colorpicker"; static const char* elemButtonGroup = "buttongroup"; static const char* elemSplitButton = "splitbutton"; static const char* elemRibbonGallery = "ribbongallery"; static const char* elemGalleryItem = "galleryitem"; static const char* elemComboBoxItem = "comboboxitem"; static const char* elemGroupOptionsMenu= "groupoptionsmenu"; static const char* attribName = "name"; static const char* attribText = "text"; static const char* attribStyle = "style"; static const char* attribPopupStyle = "popupStyle"; static const char* attribToolTip = "tooltip"; static const char* attribIcon = "icon"; static const char* attribIconWidth = "width"; static const char* attribIconHeight = "height"; static const char* attribColumns = "columns"; static const char* attribVisible = "visible"; static const char* attribCheckable = "ischeckable"; static const char* attribProxyAction = "isProxy"; static const char* attribEnabled = "enabled"; static const char* attribRequiresAuth = "requiresauthentication"; static const char* attribChecked = "checked"; static const char* attribDefaultValue = "defaultvalue"; static const char* attribIconVisibleInMenu = "iconvisibleinmenu"; static const char* attribType = "type"; static const char* attribValue = "value"; static const char* attribInstanceName = "instancename"; static const char* attribMenuTrigger = "handleMenuTrigger"; static const char* attribCommonSlot = "iscommonslot"; static const char* attribPopup = "popup"; static const char* attribSelectChangesText = "selectChangesText"; static const char* attribFFlag = "fflag"; static const char* attribWinOnly = "iswinonly"; #ifdef _WIN32 static const char* attribMacOnly = "ismaconly"; #endif static const char* attribMinButton = "minimizeButton"; static const char* attribThemeOptions = "themeoptions"; static const char* attribToolTipIcon = "tooltipicon"; static const char* attribFont = "font"; static const char* attribContextTitle = "contexttitle"; static const char* attribContextColor = "contextcolor"; static const char* attribItemWidth = "itemwidth"; static const char* attribItemHeight = "itemheight"; static const char* attribSetting = "setting"; static const char* actionCheckBox = "checkbox"; static const char* actionRadio = "radio"; static const char* actionPlugin = "plugin"; static const char* settingRibbonMinimized = "rbxRibbonMinimized"; static const char* settingRecentFiles = "rbxRecentFiles"; static const char* settingLastDirectory = "rbxl_last_directory"; RobloxRibbonMainWindow::RobloxRibbonMainWindow(RobloxMainWindow* pMainWindow) : Qtitan::RibbonMainWindow(NULL) , m_LastDirectory(".") , m_pRibbonStyle(NULL) , m_pRibbonMinimizeAction(NULL) , m_pQuickAccessConfigDialog(NULL) , m_bInitialized(false) { // DO NOT ADD MORE HERE // Try to keep most initialization logic in setupRibbonBar } void RobloxRibbonMainWindow::setupRibbonBar() { if (m_bInitialized) throw RBX::runtime_error("Ribbon bar already initialized."); //create ribbonbar QString xmlFilePath(RobloxSettings::getResourcesFolder()); xmlFilePath.append("/"); xmlFilePath.append(updatedLayoutXmlConfigFile); QFile file(xmlFilePath); if (!file.open(QIODevice::ReadOnly)) throw RBX::runtime_error("Failed to open configuration XML file."); QDomDocument doc("appRibbonBar"); if (!doc.setContent(&file)) throw RBX::runtime_error("Failed to parse configuration XML file."); QDomElement docElem = doc.documentElement(); if(docElem.tagName() != elemMainWindow) throw RBX::runtime_error("Failed to parse configuration XML file. File must start with MainWindow element!"); // initialize default data and do required cleanup initialize(); // now create system button parseAndCreateSystemButton(docElem); // finally ribbon bar parseAndCreateRibbonBar(docElem); // quickaccess bar must be created in the last, as there can be some actions created from XML file parseAndCreateQuickAccessBar(docElem); ribbonBar()->setFrameThemeEnabled(false); ribbonBar()->setTitleBarVisible(false); ribbonBar()->getSystemButton()->setToolButtonStyle(Qt::ToolButtonTextOnly); m_bInitialized = true; } RibbonPage* RobloxRibbonMainWindow::createRibbonPage(const QString& objectName, const QString& pageTitle, int indexPosition) { RibbonPage* pRibbonPage = ribbonBar()->findChild(objectName); if (pRibbonPage) return pRibbonPage; // Pages should be tacked onto the end of the bar, unless an explicit index has been passed in. int insertedPosition = indexPosition >= 0 ? indexPosition : ribbonBar()->getPageCount(); pRibbonPage = ribbonBar()->insertPage(insertedPosition, pageTitle); RBXASSERT(pRibbonPage); pRibbonPage->setObjectName(objectName); // update the index property of this page and all pages after for (int ii = insertedPosition; ii < ribbonBar()->getPageCount(); ii++) ribbonBar()->getPage(ii)->setProperty("index", ii); return pRibbonPage; } RibbonGroup* RobloxRibbonMainWindow::createRibbonGroup(RibbonPage* pParent, const QString& objectName, const QString& groupTitle) { if (!pParent) return NULL; RibbonGroup* pRibbonGroup = pParent->findChild(objectName); if (pRibbonGroup) return pRibbonGroup; pRibbonGroup = pParent->addGroup(groupTitle.isEmpty() ? objectName : groupTitle); RBXASSERT(pRibbonGroup); pRibbonGroup->setObjectName(objectName); return pRibbonGroup; } void RobloxRibbonMainWindow::parseAndCreateQuickAccessBar(const QDomElement &docElem) { QDomElement quickAccessBarElement = docElem.firstChildElement(elemQuickAccessBar); if (quickAccessBarElement.isNull()) return; Qtitan::RibbonQuickAccessBar* pQuickAccessBar = ribbonBar()->getQuickAccessBar(); if (!pQuickAccessBar) return; QString toolTip = getToolTip(quickAccessBarElement); if (!toolTip.isEmpty()) { QAction* pAction = pQuickAccessBar->actionCustomizeButton(); if (pAction) pAction->setToolTip(tr("Customize Quick Access Bar")); } QString visible = quickAccessBarElement.attribute(attribVisible); ribbonBar()->showQuickAccess(visible.isEmpty() || (visible != "false")); // load QuickAccess configuration from settings bool configLoaded = RobloxQuickAccessConfig::singleton().loadQuickAccessConfig(); RobloxSettings settings; // parse data from XML RobloxQuickAccessConfig::QuickAccessActionCollection defaultQuickAccessCollection; QDomElement addActionElement = quickAccessBarElement.firstChildElement(elemAddAction); while (!addActionElement.isNull()) { QString actionName = getName(addActionElement); if (!actionName.isEmpty()) { QAction *pChildAction = findChildAction(actionName); if (pChildAction) { // check if action is visible in quick access bar bool actVisible = false; if (!addActionElement.attribute(attribVisible).isEmpty()) actVisible = addActionElement.attribute(attribVisible) == "true" ; // if not loaded from settings, then add actions from XML config if (!configLoaded) { QString key("QuickAccess_" + pChildAction->text()); if (settings.contains(key)) actVisible = settings.value(key).toBool(); // legacy support RobloxQuickAccessConfig::singleton().addToQuickAccessBar(pChildAction, actVisible); } // make a list of actions defined in XML config for restore defaultQuickAccessCollection.append(RobloxQuickAccessConfig::ActionData(pChildAction, actVisible, true)); } } addActionElement = addActionElement.nextSiblingElement(elemAddAction); } // save XML config for restore RobloxQuickAccessConfig::singleton().storeDefaults(defaultQuickAccessCollection); connect(customizeQuickAccessAction, SIGNAL(triggered()), this, SLOT(customizeQuickAccess())); } void RobloxRibbonMainWindow::updateInternalWidgetsState(QAction* pAction, bool enabledState,bool checkedState) { // update internal widgets is required only if we have ribbon bar if (QAbstractButton* pCorrelaryButton = qobject_cast(m_mCorrelaryActionMap.value(pAction))) { if (QThread::currentThread() == thread()) { pCorrelaryButton->setEnabled(enabledState); pCorrelaryButton->setChecked(checkedState); } else { QMetaObject::invokeMethod(pCorrelaryButton,"setEnabled",Qt::QueuedConnection,Q_ARG(bool,enabledState)); QMetaObject::invokeMethod(pCorrelaryButton,"setChecked",Qt::QueuedConnection,Q_ARG(bool,checkedState)); } } } QString RobloxRibbonMainWindow::getDefaultSavePath() { QString doc_path = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); QDir(doc_path).mkdir("ROBLOX"); return doc_path + "/ROBLOX"; } QStringList RobloxRibbonMainWindow::getRecentFiles() const { return RobloxSettings().value(settingRecentFiles).toStringList(); } /** * Sets the current path and stores it in the settings. * If the path is null, the default path will be used. * If the path is a filename, the filename part will be stripped off. * * @param path new path for current directory */ void RobloxRibbonMainWindow::setCurrentDirectory(const QString& path) { QString newPath = path; QFileInfo info(newPath); // if it's a file, get the path if (info.isFile()) newPath = info.absolutePath(); else newPath = info.absoluteFilePath(); // if empty or doesn't exist, get the default path if ( newPath.isEmpty() || !QDir(newPath).exists() ) newPath = getDefaultSavePath(); // don't change to the auto-save path if ( newPath == AuthoringSettings::singleton().autoSaveDir.absolutePath() ) return; m_LastDirectory = newPath; QDir::setCurrent(m_LastDirectory); // save into settings RobloxSettings().setValue(settingLastDirectory, m_LastDirectory); } void RobloxRibbonMainWindow::updateRecentFile(const QString& fileName) { //update the recent files if(!fileName.isEmpty() && (fileName.endsWith(".rbxl", Qt::CaseInsensitive) || fileName.endsWith(".rbxlx", Qt::CaseInsensitive)) && !(fileName.endsWith("visit.rbxl", Qt::CaseSensitive)) && !(fileName.endsWith("server.rbxl", Qt::CaseSensitive))) { RobloxSettings settings; QStringList files = getRecentFiles(); files.removeAll(fileName); QFileInfo fileInfo(fileName); bool bFileExists = fileInfo.exists(); if (bFileExists && !files.contains(fileName)) files.prepend(fileName); // if it exceeds max limit then remove last while (files.size() > MAX_RECENT_FILES) files.removeLast(); // update settings settings.setValue(settingRecentFiles, files); updateRecentFilesUI(); if (bFileExists) setCurrentDirectory(fileName); } } void RobloxRibbonMainWindow::actionEvent(QActionEvent* evt) { if (!m_bInitialized || m_mProxyActions.isEmpty() || evt->type() != QEvent::ActionAdded) return; QMap::iterator iter = m_mProxyActions.find(getFullName(evt->action())); if (iter == m_mProxyActions.end()) return; iter.value()->setCheckable(evt->action()->isCheckable()); connect(iter.value(), SIGNAL(triggered()), evt->action(), SLOT(trigger())); connect(evt->action(), SIGNAL(changed()), this, SLOT(updateProxyAction())); } void RobloxRibbonMainWindow::parseAndCreateSystemButton(const QDomElement &docElem) { QDomElement systemButtonElement = docElem.firstChildElement(elemeSystemButton); if (systemButtonElement.isNull()) return; QString buttonName = getName(systemButtonElement); QString icon = systemButtonElement.attribute(attribIcon); QString helpIcon = systemButtonElement.attribute(attribToolTipIcon); QIcon iconLogo; if (!helpIcon.isEmpty()) { // if sizes of the pixmaps added in QIcon are different then the first added pix will be used in tooltip associated with system button // in case of similar sizes second pixmap is used, so make sure we have size for tooltip icon different from 32x32 QPixmap helpPixmap(helpIcon); if (!helpPixmap.isNull()) iconLogo.addPixmap(helpPixmap); } if (!icon.isEmpty()) { QPixmap iconPixmap(icon); if (!iconPixmap.isNull()) iconLogo.addPixmap(iconPixmap.scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } if(QAction* actionFile = ribbonBar()->addSystemButton(iconLogo, buttonName)) { // set current theme (default theme) setTheme(m_pRibbonStyle->getTheme()); QString tooltip = getToolTip(systemButtonElement); actionFile->setToolTip(tooltip); QDomElement popupElement = systemButtonElement.firstChildElement(elemSystemPopup); if (!popupElement.isNull()) { Qtitan::RibbonSystemPopupBar* popupBar = qobject_cast(actionFile->menu()); connect(popupBar, SIGNAL(aboutToShow()), &UpdateUIManager::Instance(), SLOT(onMenuShow())); parseAndCreateChildren(popupElement, popupBar); RibbonPageSystemRecentFileList* pageRecentFile = popupBar->addPageRecentFile(tr("Recent Files")); pageRecentFile->setSize(MAX_RECENT_FILES); pageRecentFile->setObjectName("recentFileList"); connect(pageRecentFile, SIGNAL(openRecentFile(const QString&)), this, SLOT(openRecentFile(const QString&))); QAction* pActionExit = popupBar->addPopupBarAction(tr("Exit")); connect(pActionExit, SIGNAL(triggered()), this, SLOT(close())); //QAction* pActionOption = popupBar->addPopupBarAction(tr("Settings")); //connect(pActionOption, SIGNAL(triggered()), this, SLOT(openSettingsDialog())); } } } void RobloxRibbonMainWindow::parseAndCreateRibbonBar(const QDomElement &docElem) { QDomElement ribbonBarElement = docElem.firstChildElement(elemRibbonBar); if (!ribbonBarElement.isNull()) { // Add online help button ribbonBar()->addAction(onlineHelpAction, Qt::ToolButtonIconOnly); onlineHelpAction->setIcon(QIcon(":/RibbonBar/images/Studio Ribbon Icons/about.png")); // Add theme style options if (ribbonBarElement.attribute(attribThemeOptions) == "true") createThemeOptions(); // Add a minimize button if (ribbonBarElement.attribute(attribMinButton) == "true") { m_pRibbonMinimizeAction = ribbonBar()->addAction(QIcon(":/RibbonBar/images/Studio Ribbon Icons/ribbonMinimize.png"), "Toggle ribbon visiblity", Qt::ToolButtonIconOnly); m_pRibbonMinimizeAction->setStatusTip(tr("Show only the tab names on the Ribbon")); m_pRibbonMinimizeAction->setShortcut(tr("Ctrl+F1")); if (FFlag::StudioSeparateActionByActivationMethod) m_pRibbonMinimizeAction->setObjectName("minimizeRibbonAction"); QtUtilities::setActionShortcuts(*m_pRibbonMinimizeAction, m_pRibbonMinimizeAction->shortcuts()); if (FFlag::StudioSeparateActionByActivationMethod) connect(m_pRibbonMinimizeAction, SIGNAL(triggered()), this, SLOT(commonSlot())); else connect(m_pRibbonMinimizeAction, SIGNAL(triggered()), this, SLOT(toggleRibbonMinimize())); connect(ribbonBar(), SIGNAL(minimizationChanged(bool)), this, SLOT(handleRibbonMinimizationChanged(bool))); // add this to registry RobloxSettings settings; ribbonBar()->setMinimized(settings.value(settingRibbonMinimized, false).toBool()); } // Create our tab pages and their children, recursively parseAndCreateChildren(ribbonBarElement, ribbonBar()); if (ribbonBarElement.hasAttribute(attribFont)) { QString fontStr = ribbonBarElement.attribute(attribFont); if (fontStr.isEmpty()) { updateFonts(m_ribbonFont); } else { QFont font; font.fromString(fontStr); #ifdef Q_WS_MAC // On Mac we need to have bigger fonts font.setPointSize(font.pointSize() + 4); #endif // Now update fonts updateFonts(font); } } } } void RobloxRibbonMainWindow::parseAndCreateChildren(const QDomElement& tabPageElement, QObject* pParent) { for (QDomElement domElementIterator = tabPageElement.firstChildElement(); !domElementIterator.isNull(); domElementIterator = domElementIterator.nextSiblingElement()) { #ifdef Q_WS_MAC if (domElementIterator.attribute(attribWinOnly) == "true") continue; #else if (domElementIterator.attribute(attribMacOnly) == "true") continue; #endif //check if we need to enable any element via fflag? QString flagName = domElementIterator.attribute(attribFFlag); if (!flagName.isEmpty()) { try { bool negate = flagName.startsWith("!"); if (negate) flagName = flagName.mid(1); bool keepItem = RBX::GlobalAdvancedSettings::singleton()->getFFlag(flagName.toStdString()); if (negate) keepItem = !keepItem; if (!keepItem) continue; } catch (const std::runtime_error&) { RBXASSERT(false); } } QObject* pCreatedElement = NULL; QString domElementTagName = domElementIterator.tagName().toLower(); if (domElementTagName == elemTabPage) { pCreatedElement = parseAndCreateRibbonPage(domElementIterator); } else if (domElementTagName == elemGroup) { pCreatedElement = parseAndCreateRibbonGroup(domElementIterator, pParent); } else if (domElementTagName == elemColorPicker) { pCreatedElement = parseAndCreateColorPicker(domElementIterator, pParent); } else if (domElementTagName == elemMenuPopupButton) { pCreatedElement = parseAndCreateMenuPopupButton(domElementIterator, pParent); } else if (domElementTagName == elemMenuButton) { pCreatedElement = parseAndCreateMenu(domElementIterator, pParent); } else if (domElementTagName == elemSplitButton) { pCreatedElement = parseAndCreateSplitButton(domElementIterator, pParent); } else if (domElementTagName == elemRibbonGallery) { pCreatedElement = parseAndCreateRibbonGallery(domElementIterator, pParent); } else if (domElementTagName == elemAddAction) { QString actionType = domElementIterator.attribute(attribType); if (actionType == actionCheckBox) { pCreatedElement = parseAndCreateCheckBox(domElementIterator, pParent); } else if (actionType == actionRadio) { pCreatedElement = parseAndCreateRadioButton(domElementIterator, pParent); } else if ((actionType == actionPlugin) || actionType.isNull()) { pCreatedElement = parseAndCreateRibbonAction(domElementIterator, pParent); } else { throw RBX::runtime_error("Invalid action type: %s", actionType.toStdString().c_str()); } } else if (domElementTagName == elemAddSeparator) { pCreatedElement = parseAndCreateSeparator(domElementIterator, pParent); } else if (domElementTagName == elemActionGroup) { pCreatedElement = parseAndCreateActionGroup(domElementIterator, pParent); } else if (domElementTagName == elemButtonGroup) { pCreatedElement = parseAnCreateButtonGroup(domElementIterator, dynamic_cast(pParent)); } else if (domElementTagName == elemAddRadioButton) { pCreatedElement = parseAndCreateRadioButton(domElementIterator, pParent); } else if (domElementTagName == elemGalleryItem) { parseAndCreateGalleryItem(domElementIterator, pParent); } else if (domElementTagName == elemAddCheckBox) { pCreatedElement = parseAndCreateCheckBox(domElementIterator, pParent, false); } else if (domElementTagName == elemAddComboBox) { pCreatedElement = parseAndCreateComboBox(domElementIterator, pParent); } else if (domElementTagName == elemGroupOptionsMenu) { pCreatedElement = parseAndCreateGroupOptionsMenu(domElementIterator, pParent); } else if (domElementTagName == elemSubMenu) { pCreatedElement = parseAndCreateSubMenu(domElementIterator, pParent); } // add dynamic properties if any if (pCreatedElement) { if (!domElementIterator.attribute(attribValue).isEmpty()) pCreatedElement->setProperty("user_value", domElementIterator.attribute(attribValue)); if (!domElementIterator.attribute(attribVisible).isEmpty()) pCreatedElement->setProperty(attribVisible, domElementIterator.attribute(attribVisible) == "true"); if (!domElementIterator.attribute(attribEnabled).isEmpty()) pCreatedElement->setProperty(attribEnabled, domElementIterator.attribute(attribEnabled) == "true"); if (!domElementIterator.attribute(attribRequiresAuth).isEmpty()) pCreatedElement->setProperty(attribRequiresAuth, domElementIterator.attribute(attribRequiresAuth) == "true"); } } } ///// Create ribbon element functions ///// RibbonPage* RobloxRibbonMainWindow::parseAndCreateRibbonPage(const QDomElement& domElement) { // See if it exists first QString objectName = getName(domElement); RBXASSERT(!objectName.isEmpty()); QString pageTitle = getText(domElement); if (pageTitle.isNull()) pageTitle = objectName; RibbonPage* pRibbonPage = createRibbonPage(objectName, pageTitle); // Check to see if this is a context tab QString contextTitle = domElement.attribute(attribContextTitle); if (!contextTitle.isEmpty()) { pRibbonPage->setContextTitle(contextTitle); //(http://www.devmachines.com/productdocs/ribbon/qtitan.html#ContextColor-enum) if (!domElement.attribute(attribContextColor).isEmpty()) pRibbonPage->setContextColor((Qtitan::RibbonPage::ContextColor)domElement.attribute(attribContextColor).toInt()); } //now recursively create child elements parseAndCreateChildren(domElement, pRibbonPage); return pRibbonPage; } Qtitan::RibbonGroup* RobloxRibbonMainWindow::parseAndCreateRibbonGroup(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonPage* pParentPage = dynamic_cast(pParent); if (!pParentPage) throw RBX::runtime_error("Group parent must be a RibbonPage!"); Qtitan::RibbonGroup* pRibbonGroup = createRibbonGroup(pParentPage, getName(domElement)); RBXASSERT(pRibbonGroup); pRibbonGroup->setBaseSize(getSize(domElement, QSize(100, 50))); pRibbonGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); if (domElement.attribute("options") == "true") pRibbonGroup->setOptionButtonVisible(); //now recursively create child elements parseAndCreateChildren(domElement, pRibbonGroup); return pRibbonGroup; } QWidget* RobloxRibbonMainWindow::parseAndCreateColorPicker(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("Menu buttons must have a ribbon group parent element!"); QString text = getText(domElement); QIcon icon = getIcon(domElement); if (icon.isNull() && text.isEmpty()) throw RBX::runtime_error("Colorpicker must have an icon or text!"); QMenu* pDummyMenu = new QMenu; connect(pDummyMenu, SIGNAL(aboutToShow ()), this, SLOT(handleColorPickMenu())); QAction* pAction = getAction(domElement); if (!pAction) throw RBX::runtime_error("Failed to create action: %s", getName(domElement).toStdString().c_str()); QAction* pAddedAction = pRibbonGroup->addAction(pAction, getStyle(domElement), pDummyMenu); pAddedAction->setIcon(icon); pAddedAction->setText(text); pAddedAction->setObjectName(getName(domElement)); pAddedAction->setToolTip(getToolTip(domElement)); QLayoutItem* pLayoutItem = pRibbonGroup->layout()->itemAt(pRibbonGroup->layout()->count()-1); QToolButton* pToolButton = qobject_cast(pLayoutItem ? pLayoutItem->widget() : NULL); if (pToolButton) { if (!domElement.attribute(attribCheckable).isEmpty() && (domElement.attribute(attribCheckable) == "true")) { pToolButton->defaultAction()->setObjectName(getName(domElement)); pToolButton->defaultAction()->setCheckable(true); pToolButton->setCheckable(true); } pDummyMenu->setParent(pToolButton); pToolButton->setObjectName(getName(domElement)); connect(pToolButton, SIGNAL(clicked()), this, SLOT(handleToolButtonClicked())); } return pToolButton; } QMenu* RobloxRibbonMainWindow::parseAndCreateMenuPopupButton(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("Menu buttons must have a ribbon group parent element!"); QString text = getText(domElement); QIcon icon = getIcon(domElement); if (icon.isNull() && text.isEmpty()) throw RBX::runtime_error("Drop downs must have an icon or text!"); Qt::ToolButtonStyle style = getStyle(domElement); QToolButton::ToolButtonPopupMode popupMode = getPopupStyle(domElement); QString objectName = getName(domElement); ToolButtonProxyMenu* menu = new ToolButtonProxyMenu(objectName, pRibbonGroup); QAction* action = menu->menuAction(); action->setText(text); if (menu->selectChangesText && !domElement.attribute(attribSelectChangesText).isEmpty() && domElement.attribute(attribSelectChangesText) == "true") { menu->selectChangesText = true; } action->setIcon(icon); action->setToolTip(getToolTip(domElement)); pRibbonGroup->addAction(action); QString settingName; if (domElement.attribute(attribSetting) == "true") { settingName = objectName + "_RibbonSetting"; } QLayoutItem* item = pRibbonGroup->layout()->itemAt(pRibbonGroup->layout()->count()-1); if (QToolButton* widgetAction = qobject_cast(item ? item->widget() : Q_NULL)) { if (!domElement.attribute(attribCheckable).isEmpty() && (domElement.attribute(attribCheckable) == "true")) { widgetAction->defaultAction()->setObjectName(getName(domElement)); widgetAction->defaultAction()->setCheckable(true); widgetAction->setCheckable(true); } widgetAction->setToolButtonStyle(style); widgetAction->setPopupMode(popupMode); connect(widgetAction, SIGNAL(clicked()), this, SLOT(handleToolButtonClicked())); widgetAction->setObjectName(objectName); menu->toolButton = widgetAction; menu->settingName = settingName; } menu->setObjectName(objectName); menu->menuAction()->setObjectName(objectName); // check if we need to handle menu execution at top level if (!domElement.attribute(attribMenuTrigger).isEmpty() && domElement.attribute(attribMenuTrigger) == "true") connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(handleActionSelected(QAction*))); // now recursively create child elements parseAndCreateChildren(domElement, menu); QString value = domElement.attribute(attribValue); if (!settingName.isEmpty()) { value = RobloxSettings().value(settingName, value).toString(); } action->setProperty("selectedItem", value); QList actions = menu->actions(); for (QList::iterator iter = actions.begin(); iter != actions.end(); iter++) { if ((*iter)->objectName() == value) { menu->toolButton->connect(menu->toolButton, SIGNAL(clicked()), *iter, SLOT(trigger())); menu->connect(*iter, SIGNAL(changed()), menu, SLOT(onChanged())); menu->boundAction = *iter; QString iconPath = (*iter)->property("iconPath").toString(); QSize iconSize = menu->toolButton->icon().availableSizes().first(); menu->toolButton->defaultAction()->setIcon(QIcon(QPixmap(iconPath).scaled(iconSize))); if (!settingName.isEmpty()) { RobloxSettings().setValue(settingName, value); } } } return menu; } void ToolButtonProxyMenu::onChanged() { QAction* action = qobject_cast(sender()); if (action->isEnabled() != toolButton->isEnabled()) toolButton->setEnabled(action->isEnabled()); } QMenu* RobloxRibbonMainWindow::parseAndCreateMenu(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("Menu buttons must have a ribbon group parent element!"); QString text = getText(domElement); QIcon icon = getIcon(domElement); if ( icon.isNull() && text.isEmpty() ) throw RBX::runtime_error("Drop downs must have an icon or text!"); QMenu* pMenu = pRibbonGroup->addMenu(icon, text, getStyle(domElement)); connect(pMenu, SIGNAL(aboutToShow()), &UpdateUIManager::Instance(), SLOT(onMenuShow())); RBXASSERT(pMenu); RBXASSERT(pMenu->menuAction()); QString objectName = getName(domElement); pMenu->setObjectName(objectName); pMenu->menuAction()->setToolTip(getToolTip(domElement)); pMenu->menuAction()->setObjectName(objectName); // check if we need to handle menu execution at top level if (!domElement.attribute(attribMenuTrigger).isEmpty() && domElement.attribute(attribMenuTrigger) == "true") connect(pMenu, SIGNAL(triggered(QAction*)), this, SLOT(handleMenuActionTrigger(QAction*))); // now recursively create child elements if (objectName == "emulateDeviceAction") StudioDeviceEmulator::Instance().addEmulationChildren(this, pMenu); parseAndCreateChildren(domElement, pMenu); if ( !domElement.attribute(attribCheckable).isEmpty() ) pMenu->menuAction()->setCheckable(domElement.attribute(attribCheckable) == "true"); if (domElement.attribute(attribIconVisibleInMenu) == "false") { QList actions = pMenu->actions(); for (int ii = 0; ii < actions.size(); ii++) actions[ii]->setIconVisibleInMenu(false); } return pMenu; } Qtitan::RibbonGallery* RobloxRibbonMainWindow::parseAndCreateRibbonGallery(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("Gallery parent must be a ribbon group!"); Qtitan::RibbonGallery* pGallery = new Qtitan::RibbonGallery; pGallery->setObjectName(getName(domElement)); pGallery->setScrollBarPolicy(Qt::ScrollBarAsNeeded); Qtitan::RibbonGalleryGroup* pGalleryGroup = Qtitan::RibbonGalleryGroup::createGalleryGroup(); pGalleryGroup->setClipItems(false); // Set up the size of each color spot and populate colors QSize itemSize(domElement.attribute(attribItemWidth).toInt(), domElement.attribute(attribItemHeight).toInt()); pGalleryGroup->setSize(itemSize.isNull() ? QSize(16, 16) : itemSize); pGallery->setGalleryGroup(pGalleryGroup); // check if we need to create color gallery (special case) if (domElement.attribute(attribType) == "color") { GalleryItemColor::addStandardColors(pGalleryGroup); pGallery->setCheckedIndex(12); } else { parseAndCreateChildren(domElement, pGalleryGroup); pGallery->setCheckedIndex(0); } RBXASSERT(pGalleryGroup->getItemCount()); int numColumns = domElement.attribute(attribColumns).toInt(); if (!numColumns) numColumns = 5; int galleryWidth = (itemSize.width() * numColumns); int galleryHeight = (qCeil(pGalleryGroup->getItemCount()/(float)numColumns)*itemSize.height()); if (!domElement.attribute(attribPopup).isEmpty() && domElement.attribute(attribPopup).toLower() == "false") { if (galleryHeight > 64) { galleryHeight = 64; galleryWidth = galleryWidth + qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); pGallery->setScrollBarPolicy(Qt::ScrollBarAlwaysOn); } pGallery->setBaseSize(QSize(galleryWidth, galleryHeight)); pRibbonGroup->addWidget(pGallery); } else { pGallery->setBaseSize(QSize(galleryWidth+4, galleryHeight+2)); OfficePopupMenu* pPopupMenu = OfficePopupMenu::createPopupMenu(ribbonBar()); pPopupMenu->addWidget(pGallery); QAction* pAction = pRibbonGroup->addAction(getIcon(domElement), getText(domElement), getStyle(domElement), pPopupMenu, QToolButton::InstantPopup); pAction->setObjectName(getName(domElement)); } connect(pGallery, SIGNAL(itemClicked(RibbonGalleryItem*)), this, SLOT(handleBasicObjectsGalleryItemClicked(RibbonGalleryItem*))); return pGallery; } Qtitan::RibbonGalleryItem* RobloxRibbonMainWindow::parseAndCreateGalleryItem(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGalleryGroup* pGalleryGroup = qobject_cast(pParent); if (!pGalleryGroup) throw RBX::runtime_error("galleryitem must be beneath a gallery group"); Qtitan::RibbonGalleryItem* pGalleryItem = new StudioGalleryItem; pGalleryItem->setCaption(getText(domElement)); pGalleryItem->setToolTip(getToolTip(domElement)); pGalleryItem->setSizeHint(pGalleryGroup->getSize()); pGalleryItem->setData(GALLERY_ITEM_USER_DATA, domElement.attribute(attribValue)); QIcon icon = getIcon(domElement); if (!icon.isNull()) pGalleryItem->setIcon(icon); pGalleryGroup->appendItem(pGalleryItem); return pGalleryItem; } QActionGroup* RobloxRibbonMainWindow::parseAndCreateActionGroup(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pGroup = qobject_cast(pParent); QMenu* pMenu = qobject_cast(pParent); if (!pGroup && !pMenu) throw RBX::runtime_error("actiongroup elements must be beneath a group or menu"); // Qt doesn't let us add same actions in multiple action groups, so we must reuse the already created action group QString objectName = getName(domElement); QActionGroup* pActionGroup = findChild(objectName); if (!pActionGroup) { pActionGroup = new QActionGroup(this); pActionGroup->setObjectName(objectName); bool isSingleSelect = domElement.attribute(attribType) == "singleselect"; pActionGroup->setExclusive(!isSingleSelect); pActionGroup->setProperty("singleselect", isSingleSelect); connect(pActionGroup, SIGNAL(selected(QAction*)), this, SLOT(handleActionGroupSelected(QAction*))); } // we will be parsing actions to a dummy action group and later add the created actions to the original action group QActionGroup dummyActionGroup(this); //now recursively create child elements parseAndCreateChildren(domElement, &dummyActionGroup); bool isCheckable = domElement.attribute(attribCheckable) == "true"; QAction* pAction = NULL; QList actions = dummyActionGroup.actions(); for (int ii = 0; ii < actions.size(); ii++) { pAction = actions.at(ii); pAction->setCheckable(isCheckable); // add action to correct parent i.e. Ribbon Group or Menu if (pGroup) pGroup->addAction(pAction, getStyle(domElement)); else pMenu->addAction(pAction); // add actions to the original group (NOTE: dummy action group will not be having these actions anymore after this) pActionGroup->addAction(pAction); } return pActionGroup; } QButtonGroup* RobloxRibbonMainWindow::parseAnCreateButtonGroup(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("buttongroup must have a ribbon group parent element!"); QButtonGroup* pButtonGroup = new QButtonGroup(this); pButtonGroup->setObjectName(getName(domElement)); parseAndCreateChildren(domElement, pButtonGroup); // add created buttons to ribbon group QList buttons = pButtonGroup->buttons(); if (buttons.size() > 0) { for (int ii = 0; ii < buttons.size(); ++ii) { // populate default value if any if (buttons.at(ii)->isChecked()) populateNameValueStore(pButtonGroup->objectName(), buttons.at(ii)); // add widget to ribbon group pRibbonGroup->addWidget(buttons.at(ii)); } connect(pButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(handleButtonGroupSelected(QAbstractButton*))); } return pButtonGroup; } QAction* RobloxRibbonMainWindow::parseAndCreateSplitButton(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("splitbutton must have a ribbon group parent element!"); QMenu* pMenu = new QMenu(this); parseAndCreateChildren(domElement, pMenu); QAction* pAction = pRibbonGroup->addAction(getIcon(domElement), getText(domElement), getStyle(domElement), pMenu); pAction->setObjectName(getName(domElement)); if (!FFlag::StudioSeparateActionByActivationMethod) connect(pAction, SIGNAL(triggered(bool)), this, SLOT(commonSlot(bool))); return pAction; } QAction* RobloxRibbonMainWindow::parseAndCreateSeparator(const QDomElement& domElement, QObject* pParent) { if (QMenu* pParentMenu = qobject_cast(pParent)) return pParentMenu->addSeparator(); if (Qtitan::RibbonGroup* pParentGroup = qobject_cast(pParent)) return pParentGroup->addSeparator(); //none of the above - throw throw RBX::runtime_error("addseparator elements must be beneath a menu or group!"); } QRadioButton* RobloxRibbonMainWindow::parseAndCreateRadioButton(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); QButtonGroup* pButtonGroup = qobject_cast(pParent); if (!pRibbonGroup && !pButtonGroup) throw RBX::runtime_error("radio buttons must be beneath a button/ribbon group"); QString objectName = getName(domElement); if (objectName.isEmpty()) throw RBX::runtime_error("radio button name can not be empty!"); // Create the button QRadioButton* pRadioButton = new QRadioButton; pRadioButton->setObjectName(objectName); pRadioButton->setToolTip(getToolTip(domElement)); pRadioButton->setText(getText(domElement)); pRadioButton->setChecked(domElement.attribute(attribChecked) == "true"); if (pRibbonGroup) addWidgetToRibbonGroup(pRibbonGroup, pRadioButton); else addButtonToButtonGroup(pButtonGroup, pRadioButton); QAction* pAction = getAction(domElement); if (!pAction) throw RBX::runtime_error("Failed to create action: %s", getName(domElement).toStdString().c_str()); connect(pRadioButton, SIGNAL(clicked()), pAction, SLOT(trigger())); if (!FFlag::StudioSeparateActionByActivationMethod) connect(pAction, SIGNAL(triggered()), pRadioButton, SLOT(toggle())); return pRadioButton; } QCheckBox* RobloxRibbonMainWindow::parseAndCreateCheckBox(const QDomElement& domElement, QObject* pParent, bool actionMode) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("checkbox must be beneath a ribbon group"); QString objectName = getName(domElement); if (objectName.isEmpty()) throw RBX::runtime_error("checkbox name element can not be empty!"); // Create button QCheckBox* pCheckBox = new QCheckBox; pCheckBox->setObjectName(getName(domElement)); pCheckBox->setText(getText(domElement)); pCheckBox->setToolTip(getToolTip(domElement)); pCheckBox->setChecked(domElement.attribute(attribChecked) == "true"); // add to ribbon group addWidgetToRibbonGroup(pRibbonGroup, pCheckBox); // if no action is associated then connect with clicked signal to populate valuestore if (!actionMode) connect(pCheckBox, SIGNAL(clicked(bool)), this, SLOT(handleCheckBoxClicked(bool))); return pCheckBox; } QComboBox* RobloxRibbonMainWindow::parseAndCreateComboBox(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("checkbox must be beneath a ribbon group"); QString objectName = getName(domElement); if (objectName.isEmpty()) throw RBX::runtime_error("checkbox name element can not be empty!"); // Create button QComboBox* pComboBox = new QComboBox; pComboBox->setObjectName(getName(domElement)); // add combobox items for (QDomElement domElementIterator = domElement.firstChildElement(); !domElementIterator.isNull(); domElementIterator = domElementIterator.nextSiblingElement()) { if (domElementIterator.tagName().toLower() == elemComboBoxItem) { pComboBox->addItem(getText(domElementIterator)); // check if there's any user value if (!domElementIterator.attribute(attribValue).isEmpty()) pComboBox->setItemData(pComboBox->count() - 1, domElementIterator.attribute(attribValue)); // if this index is default if (domElementIterator.attribute(attribDefaultValue) == "true") pComboBox->setCurrentIndex(pComboBox->count() - 1); } } // add to ribbon group addWidgetToRibbonGroup(pRibbonGroup, pComboBox); connect(pComboBox, SIGNAL(activated(int)), this, SLOT(handleComboBoxActivated(int))); return pComboBox; } QAction* RobloxRibbonMainWindow::parseAndCreateRibbonAction(const QDomElement& domElement, QObject* pParent) { if (!pParent) throw RBX::runtime_error("Failed to create action: %s. No parent element provided!", getName(domElement).toStdString().c_str()); QAction* pAction = getAction(domElement); if (!pAction) throw RBX::runtime_error("Failed to create action: %s", getName(domElement).toStdString().c_str()); addActionToParent(pAction, pParent, domElement); // If we have verbs that handle these actions, we just need to connect these actions to the common slot, // which will invoke the associated verb. In the future we should consider making the verb list in the common // slot just a list of strings instead of actual QAction*s. That way we can add these actions to that list // instead of marking them as "iscommonslot=true" in the RobloxStudioRibbon.xml file. if (!FFlag::StudioSeparateActionByActivationMethod && domElement.attribute(attribCommonSlot) == "true") connect(pAction, SIGNAL(triggered(bool)), this, SLOT(commonSlot(bool))); return pAction; } QMenu* RobloxRibbonMainWindow::parseAndCreateGroupOptionsMenu(const QDomElement& domElement, QObject* pParent) { Qtitan::RibbonGroup* pRibbonGroup = qobject_cast(pParent); if (!pRibbonGroup) throw RBX::runtime_error("optionsMenu must be beneath a ribbon group"); QAction* optionButtonAction = pRibbonGroup->getOptionButtonAction(); if (!optionButtonAction) throw RBX::runtime_error("optionsMenu must has an action to associate"); QMenu* pMenu = new QMenu(pRibbonGroup); pMenu->setObjectName(QString("__%1Group-Menu_").arg(pRibbonGroup->objectName())); parseAndCreateChildren(domElement, pMenu); if (!pMenu->isEmpty()) { pMenu->setProperty("useActionName", true); connect(pMenu, SIGNAL(aboutToShow()), &UpdateUIManager::Instance(), SLOT(onMenuShow())); optionButtonAction->setToolTip(getToolTip(domElement)); connect(optionButtonAction, SIGNAL(triggered()), this, SLOT(showGroupOptionsMenu())); // check if we need to handle menu execution at top level if (!domElement.attribute(attribMenuTrigger).isEmpty() && domElement.attribute(attribMenuTrigger) == "true") connect(pMenu, SIGNAL(triggered(QAction*)), this, SLOT(handleMenuActionTrigger(QAction*))); } return pMenu; } QMenu* RobloxRibbonMainWindow::parseAndCreateSubMenu(const QDomElement& domElement, QObject* parent) { QMenu* menuParent = qobject_cast(parent); if (!menuParent) throw RBX::runtime_error("submenu must be beneath a QMenu"); QMenu* submenu = new QMenu(domElement.attribute("name")); parseAndCreateChildren(domElement, submenu); menuParent->addMenu(submenu); connect(submenu, SIGNAL(aboutToShow()), &UpdateUIManager::Instance(), SLOT(onMenuShow())); return submenu; } void RobloxRibbonMainWindow::showGroupOptionsMenu() { QAction* pSender = qobject_cast(sender()); if (pSender) { QWidget* pWidget = pSender->parentWidget(); if (pWidget) { QMenu* pMenu = pWidget->findChild(QString("__%1Group-Menu_").arg(pWidget->objectName())); if (pMenu) pMenu->exec(QCursor::pos()); } } } void RobloxRibbonMainWindow::updateProxyAction() { QAction* pAction = qobject_cast(sender()); QMap::iterator iter = m_mProxyActions.find(getFullName(pAction)); if (iter == m_mProxyActions.end()) return; iter.value()->setEnabled(pAction->isEnabled()); iter.value()->setChecked(pAction->isChecked()); iter.value()->setVisible(pAction->isVisible()); iter.value()->setIcon(pAction->icon()); } ///// Utility functions ///// QString RobloxRibbonMainWindow::getName(const QDomElement& domElement) const { return domElement.attribute(attribName); } QString RobloxRibbonMainWindow::getToolTip(const QDomElement& domElement) const { // if we've tool tip then return if (!domElement.attribute(attribToolTip).isEmpty()) return domElement.attribute(attribToolTip); // return "text"(i.e. title) if (!domElement.attribute(attribText).isEmpty()) return domElement.attribute(attribText); // nothing, return name return getName(domElement); } QString RobloxRibbonMainWindow::getText(const QDomElement& domElement) const { if (!domElement.attribute(attribText).isEmpty()) return domElement.attribute(attribText); return domElement.attribute(attribName); } QSize RobloxRibbonMainWindow::getSize(const QDomElement& domElement, const QSize& defaultSize) const { QSize size(defaultSize); int width = domElement.attribute(attribIconWidth).toInt(); if (width > 0) size.setWidth(width); int height = domElement.attribute(attribIconHeight).toInt(); if (height > 0) size.setHeight(height); return size; } QIcon RobloxRibbonMainWindow::getIcon(const QDomElement& domElement, const QSize& defaultSize) const { QSize size(getSize(domElement, defaultSize)); QPixmap pixmap(domElement.attribute(attribIcon)); if (!pixmap.isNull()) pixmap = pixmap.scaled(size); return QIcon(pixmap); } Qt::ToolButtonStyle RobloxRibbonMainWindow::getStyle( const QDomElement& domElement ) const { QString style = domElement.attribute(attribStyle); if (style == "icononly") return Qt::ToolButtonIconOnly; if (style == "textonly") return Qt::ToolButtonTextOnly; if (style == "textbesideicon") return Qt::ToolButtonTextBesideIcon; if (style == "textundericon") return Qt::ToolButtonTextUnderIcon; return Qt::ToolButtonFollowStyle; } enum QToolButton::ToolButtonPopupMode RobloxRibbonMainWindow::getPopupStyle(const QDomElement& domElement) const { QString style = domElement.attribute(attribPopupStyle); if (style == "delayed") return QToolButton::DelayedPopup; if (style == "menubutton") return QToolButton::MenuButtonPopup; if (style == "instant") return QToolButton::InstantPopup; return QToolButton::InstantPopup; } QAction* RobloxRibbonMainWindow::getAction(const QDomElement& domElement) { QString objectName = getName(domElement); if (objectName.isEmpty()) return NULL; QAction* pAction = findChildAction(objectName); if (!pAction) { if (domElement.attribute(attribType) == "plugin") { pAction = new PluginAction(getText(domElement), this); } else { pAction = new QAction(getText(domElement), this); } // add newly created actions to mainwindow, so it is available for customization addAction(pAction); pAction->setObjectName(objectName); pAction->setEnabled(true); pAction->setVisible(true); } if ( !domElement.attribute(attribText).isEmpty() ) pAction->setText(domElement.attribute(attribText)); if ( !domElement.attribute(attribIcon).isEmpty()) pAction->setProperty("iconPath", domElement.attribute(attribIcon)); QIcon icon = getIcon(domElement); if (!icon.isNull()) pAction->setIcon(icon); if ( !domElement.attribute(attribToolTip).isEmpty() ) pAction->setToolTip(domElement.attribute(attribToolTip)); if ( !domElement.attribute(attribInstanceName).isEmpty() ) pAction->setData(domElement.attribute(attribInstanceName)); if ( !domElement.attribute(attribCheckable).isEmpty() ) pAction->setCheckable(domElement.attribute(attribCheckable) == "true"); if (domElement.attribute(attribType) == "plugin") { RobloxQuickAccessConfig& quickAccessConfig = RobloxQuickAccessConfig::singleton(); if (QAction* mappedAction = quickAccessConfig.mapProxyAction(pAction)) quickAccessConfig.addToQuickAccessBar(mappedAction, quickAccessConfig.visibleInQuickAccessBar(pAction)); } if (domElement.attribute(attribProxyAction) == "true") { // since this action is a proxy action, it's source action will be added to quick access dialog pAction->setProperty("disableForQuickAccess", true); m_mProxyActions.insert(objectName, pAction); } return pAction; } void RobloxRibbonMainWindow::addWidgetToRibbonGroup(Qtitan::RibbonGroup* pRibbonGroup, QWidget* pWidget) { if (!pRibbonGroup || !pWidget) return; QAction* pAction = pRibbonGroup->addWidget(pWidget); pAction->setObjectName(pWidget->objectName()); pAction->setCheckable(true); QAction* pMainWindowAction = findChildAction(pWidget->objectName()); if (pMainWindowAction) { /* * This sets up bidirectional trigger handling to make sure menu actions are in sync with existing action. * This is because we have to have separate actions for displaying some buttons on the ribbon bar than * the existing action that actually handles the execution. */ m_mCorrelaryActionMap.insert(pMainWindowAction, pWidget); //execute action if (qobject_cast(pWidget)) connect(pWidget, SIGNAL(clicked(bool)), pMainWindowAction, SLOT(trigger())); } } void RobloxRibbonMainWindow::addButtonToButtonGroup(QButtonGroup* pButtonGroup, QAbstractButton* pButton) { // add button the ribbon group pButtonGroup->addButton(pButton); } void RobloxRibbonMainWindow::addActionToParent(QAction* pAction, QObject* pParent, const QDomElement& domElement) { if ( Qtitan::RibbonGallery* pRibbonGallery = qobject_cast(pParent) ) { RibbonGalleryGroup* pRibbonGalleryGroup = pRibbonGallery->galleryGroup(); if ( !pRibbonGalleryGroup ) throw RBX::runtime_error("Failed to create action: %s. RibbonGallery must have RibbonGalleryGroup!", qPrintable(pAction->objectName())); RibbonGalleryItem* pGalleryItem = pRibbonGalleryGroup->addItem(pAction->text(), pAction->icon().pixmap(getSize(domElement))); pGalleryItem->setData(0,pAction->data()); pGalleryItem->setToolTip(pAction->text()); pGalleryItem->setStatusTip(pAction->text()); } else if (Qtitan::RibbonGroup* ribbonGroup = qobject_cast(pParent)) { ribbonGroup->addAction(pAction, getStyle(domElement)); } else if (QWidget* pParentWidget = qobject_cast(pParent)) { pParentWidget->addAction(pAction); } else if (QActionGroup* pParentActionGroup = qobject_cast(pParent)) { pParentActionGroup->addAction(pAction); } else { pAction->setParent(pParent); } } ///// Slots ///// void RobloxRibbonMainWindow::handleActionGroupSelected(QAction *pAction) { const QActionGroup* pActionGroup = qobject_cast(sender()); if (!pActionGroup) return; //make sure we have only one action is selected if (pActionGroup->property("singleselect").toBool()) { QList actions = pActionGroup->actions(); for (int i = 0; i < actions.size(); i++) { if (actions.at(i) != pAction) actions.at(i)->setChecked(false); } } //now execute the action if (RobloxDocManager::Instance().getCurrentDoc()) { //set the dynamic properties for later use only if the action is checked if (!pAction->isCheckable() || (pAction->isCheckable() && pAction->isChecked())) populateNameValueStore(pActionGroup->objectName(), pAction); else NameValueStoreManager::singleton().clear(pActionGroup->objectName()); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pActionGroup->objectName(), pAction->isChecked()); } } void RobloxRibbonMainWindow::handleButtonGroupSelected(QAbstractButton* pButton) { const QButtonGroup* pButtonGroup = qobject_cast(sender()); if (!pButtonGroup) return; //now execute the button if (RobloxDocManager::Instance().getCurrentDoc()) { populateNameValueStore(pButtonGroup->objectName(), pButton); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pButtonGroup->objectName(), pButton->isChecked()); } } void RobloxRibbonMainWindow::handleActionSelected(QAction* pAction) { ToolButtonProxyMenu* pMenu = qobject_cast(sender()); if (!pMenu) return; QToolButton* toolButton = pMenu->toolButton; if (pMenu->boundAction) { toolButton->disconnect(toolButton, SIGNAL(clicked()), pMenu->boundAction, SLOT(trigger())); pMenu->disconnect(pMenu->boundAction, SIGNAL(changed()), pMenu, SLOT(onChanged())); } pMenu->toolButton->connect(pMenu->toolButton, SIGNAL(clicked()), pAction, SLOT(trigger())); pMenu->connect(pAction, SIGNAL(changed()), pMenu, SLOT(onChanged())); pMenu->boundAction = pAction; if (!pMenu->settingName.isEmpty()) { RobloxSettings().setValue(pMenu->settingName, pAction->objectName()); } QString iconPath = pAction->property("iconPath").toString(); if (!pMenu->toolButton->icon().isNull()) { QSize iconSize = pMenu->toolButton->icon().availableSizes().first(); pMenu->toolButton->defaultAction()->setIcon(QIcon(QPixmap(iconPath).scaled(iconSize))); } if (pMenu->selectChangesText) pMenu->toolButton->setText(pAction->text()); //now execute the button if (RobloxDocManager::Instance().getCurrentDoc()) { populateNameValueStore(pMenu->objectName(), pAction); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pMenu->objectName(), pAction->isChecked()); UpdateUIManager::Instance().updateToolBars(); } } void RobloxRibbonMainWindow::handleMenuActionTrigger(QAction* pAction) { const QMenu* pMenu = qobject_cast(sender()); if (!pMenu) return; //now execute the button if (RobloxDocManager::Instance().getCurrentDoc()) { if (pMenu->property("useActionName").toBool()) { RobloxDocManager::Instance().getCurrentDoc()->handleAction(pAction->objectName(), pAction->isChecked()); UpdateUIManager::Instance().updateToolBars(); } else { populateNameValueStore(pMenu->objectName(), pAction); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pMenu->objectName(), pAction->isChecked()); } } } void RobloxRibbonMainWindow::handleBasicObjectsGalleryItemClicked(RibbonGalleryItem* item) { Qtitan::RibbonGallery* pGallery = qobject_cast(sender()); if (!pGallery) return; pGallery->setCheckedItem(item); //now execute the action if (RobloxDocManager::Instance().getCurrentDoc()) { NameValueStoreManager::singleton().setValue(pGallery->objectName(), "user_value", item->data(100)); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pGallery->objectName(), false); } } void RobloxRibbonMainWindow::handleCheckBoxClicked(bool state) { const QCheckBox* pCheckBox = qobject_cast(sender()); if (!pCheckBox) return; if (RobloxDocManager::Instance().getCurrentDoc()) { NameValueStoreManager::singleton().setValue(pCheckBox->objectName(), "enabled", pCheckBox->isEnabled()); NameValueStoreManager::singleton().setValue(pCheckBox->objectName(), "checked", pCheckBox->isChecked()); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pCheckBox->objectName(), false); } } void RobloxRibbonMainWindow::handleComboBoxActivated(int index) { const QComboBox* pComboBox = qobject_cast(sender()); if (!pComboBox) return; if (RobloxDocManager::Instance().getCurrentDoc()) { if (index < 0) { NameValueStoreManager::singleton().clear(pComboBox->objectName()); } else { NameValueStoreManager::singleton().setValue(pComboBox->objectName(), "index", index); NameValueStoreManager::singleton().setValue(pComboBox->objectName(), "user_value", pComboBox->itemData(index)); } } } void RobloxRibbonMainWindow::handleItemSelected(const QString& selectedItem) { QToolButton* item = qobject_cast(sender()); if (!item || !item->defaultAction()) return; // Now execute the action. if (RobloxDocManager::Instance().getCurrentDoc()) { NameValueStoreManager::singleton().setValue(item->objectName(), "user_value", selectedItem); item->defaultAction()->setProperty("selectedItem", selectedItem); RobloxDocManager::Instance().getCurrentDoc()->handleAction(item->objectName(), false); UpdateUIManager::Instance().updateToolBars(); } } void RobloxRibbonMainWindow::handleToolButtonClicked() { QToolButton* item = qobject_cast(sender()); if (!item || !item->defaultAction()) return; handleItemSelected(item->defaultAction()->property("selectedItem").toString()); } void RobloxRibbonMainWindow::handleColorPickMenu() { QMenu* pMenu = qobject_cast(sender()); QToolButton* pButton = pMenu ? qobject_cast(pMenu->parent()) : NULL; if (!pMenu || !pButton) return; // create colorpicker frame and it show at appropriate location ColorPickerFrame *pPickerFrame = new ColorPickerFrame(this); QVariant var = pButton->defaultAction()->property("selectedIndex"); if (!var.isNull() && var.isValid()) pPickerFrame->setSelectedIndex(var.toInt()); pPickerFrame->move(pButton->mapToGlobal(pButton->rect().bottomLeft())); pPickerFrame->show(); // create an event loop QEventLoop* eventLoop = new QEventLoop(pPickerFrame); pPickerFrame->setEventLoop(eventLoop); eventLoop->exec(); // get selected color int selectedColor = pPickerFrame->getSelectedItem(); // let document handle the selection if (selectedColor != -1 && RobloxDocManager::Instance().getCurrentDoc()) { NameValueStoreManager::singleton().setValue(pButton->objectName(), "user_value", selectedColor); pButton->defaultAction()->setProperty("selectedItem", selectedColor); pButton->defaultAction()->setProperty("selectedIndex", pPickerFrame->getSelectedIndex()); RobloxDocManager::Instance().getCurrentDoc()->handleAction(pButton->objectName(), false); UpdateUIManager::Instance().updateToolBars(); } // cleanup delete eventLoop; delete pPickerFrame; pMenu->hide(); pButton->setDown(false); } void RobloxRibbonMainWindow::handleRibbonMinimizationChanged(bool minimized) { m_pRibbonMinimizeAction->setChecked(minimized); m_pRibbonMinimizeAction->setIcon(minimized ? QIcon(":/RibbonBar/images/Studio Ribbon Icons/ribbonMaximize.png") : QIcon(":/RibbonBar/images/Studio Ribbon Icons/ribbonMinimize.png")); RobloxSettings().setValue(settingRibbonMinimized, minimized); } void RobloxRibbonMainWindow::toggleRibbonMinimize() { ribbonBar()->setMinimized(!ribbonBar()->isMinimized()); } void RobloxRibbonMainWindow::handleStyleChange(QAction* pAction) { if (!m_pRibbonStyle) return; Qtitan::RibbonStyle::Theme themeId = Qtitan::RibbonStyle::Office2010Black; if (pAction->objectName() == "Office2007Blue") themeId = Qtitan::RibbonStyle::Office2007Blue; else if (pAction->objectName() == "Office2007Black") themeId = Qtitan::RibbonStyle::Office2007Black; else if (pAction->objectName() == "Office2007Silver") themeId = Qtitan::RibbonStyle::Office2007Silver; else if (pAction->objectName() == "Office2007Aqua") themeId = Qtitan::RibbonStyle::Office2007Aqua; else if (pAction->objectName() == "Windows7Scenic") themeId = Qtitan::OfficeStyle::Windows7Scenic; else if (pAction->objectName() == "Office2010Blue") themeId = Qtitan::OfficeStyle::Office2010Blue; else if (pAction->objectName() == "Office2010Silver") themeId = Qtitan::OfficeStyle::Office2010Silver; else if (pAction->objectName() == "Office2010Black") themeId = Qtitan::OfficeStyle::Office2010Black; else if (pAction->objectName() == "Office2013White") themeId = Qtitan::OfficeStyle::Office2013White; else if (pAction->objectName() == "Office2013Gray") themeId = Qtitan::OfficeStyle::Office2013Gray; setTheme(themeId); } void RobloxRibbonMainWindow::initialize() { cleanupToolBars(); // Add all previously created actions to the ribbon so we have accelerators (even though they may not be visible) QList childActions = this->findChildren(); for (int i = 0; i < childActions.length(); i++) this->addAction(childActions.at(i)); // we do no want mainwindow to steal focus setFocusPolicy(Qt::NoFocus); QByteArray fontData = QtUtilities::getResourceFileBytes(":/fonts/SourceSansPro/TTF/SourceSansPro-Regular.ttf"); int fontIndex = QFontDatabase::addApplicationFontFromData(fontData); QStringList loadedFontFamilies = QFontDatabase::applicationFontFamilies(fontIndex); RBXASSERT(!loadedFontFamilies.empty()); m_ribbonFont = QFont(loadedFontFamilies.at(0), 9); #ifdef Q_WS_MAC // On Mac we need to have bigger fonts m_ribbonFont.setPointSize(m_ribbonFont.pointSize() + 4); QApplication::setFont(m_ribbonFont); QApplication::setFont(m_ribbonFont, "QListWidget"); #else QApplication::setFont(m_ribbonFont); #endif // create default style m_pRibbonStyle = new Qtitan::RibbonStyle(); m_pRibbonStyle->setTheme(Qtitan::OfficeStyle::Office2013White); m_pRibbonStyle->setAnimationEnabled(false); qApp->setStyle(m_pRibbonStyle); // ribbon bar object (MUST) setRibbonBar(new RibbonBar(this)); ribbonBar()->setFont(m_ribbonFont); } void RobloxRibbonMainWindow::cleanupToolBars() { QList toolbars = findChildren(); for (int ii = 0; ii < toolbars.count(); ++ii) { if (toolbars.at(ii) != commandToolBar) { removeToolBar(toolbars.at(ii)); delete toolbars.at(ii); } } } void RobloxRibbonMainWindow::updateFonts(const QFont& font) { if (font == m_ribbonFont) { QFont smallerFont(font); smallerFont.setPointSize(font.pointSize() - 1); const QList ribbonWidgets = ribbonBar()->findChildren(); for (int ii = 0; ii < ribbonWidgets.count(); ii++) { if (ribbonWidgets[ii] && ribbonWidgets[ii]->metaObject()) { #ifdef Q_WS_MAC // Mac requires setting of fonts to both i.e. Tab as well as other children ribbonWidgets[ii]->setFont(ribbonWidgets[ii]->metaObject()->className() == QString("Qtitan::RibbonTab") ? smallerFont : font); #else if (ribbonWidgets[ii]->metaObject()->className() == QString("Qtitan::RibbonTab")) ribbonWidgets[ii]->setFont(smallerFont); #endif } } RibbonSystemButton* pSystemButton = ribbonBar()->getSystemButton(); if (pSystemButton && pSystemButton->defaultAction() && pSystemButton->defaultAction()->menu()) { pSystemButton->defaultAction()->menu()->setFont(font); const QList& systemMenuActions = pSystemButton->defaultAction()->menu()->actions(); for (int ii = 0; ii < systemMenuActions.count(); ii++) { if (systemMenuActions[ii]) systemMenuActions[ii]->setFont(font); } } } else { QApplication::setFont(font); // on mac we need explicit font update #ifdef Q_WS_MAC QApplication::setFont(font, "QListWidget"); #endif QFont smallerFont(font); smallerFont.setPointSize(font.pointSize() - 1); ribbonBar()->setFont(font); const QList ribbonWidgets = ribbonBar()->findChildren(); for (int ii = 0; ii < ribbonWidgets.count(); ii++) { if (ribbonWidgets[ii] && ribbonWidgets[ii]->metaObject()) ribbonWidgets[ii]->setFont(ribbonWidgets[ii]->metaObject()->className() == QString("Qtitan::RibbonTab") ? smallerFont : font); } RibbonSystemButton* pSystemButton = ribbonBar()->getSystemButton(); if (pSystemButton && pSystemButton->defaultAction() && pSystemButton->defaultAction()->menu()) { pSystemButton->defaultAction()->menu()->setFont(font); const QList& systemMenuActions = pSystemButton->defaultAction()->menu()->actions(); for (int ii = 0; ii < systemMenuActions.count(); ii++) { if (systemMenuActions[ii]) systemMenuActions[ii]->setFont(font); } } } } void RobloxRibbonMainWindow::customizeQuickAccess() { if (!m_pQuickAccessConfigDialog) m_pQuickAccessConfigDialog = new QuickAccessConfigDialog(this); // toggle visibility if (m_pQuickAccessConfigDialog->isVisible()) { m_pQuickAccessConfigDialog->hide(); } else { m_pQuickAccessConfigDialog->show(); m_pQuickAccessConfigDialog->raise(); } } void RobloxRibbonMainWindow::createThemeOptions() { Qtitan::RibbonStyle::Theme themeId = Qtitan::RibbonStyle::Office2007Blue; if (m_pRibbonStyle) themeId = m_pRibbonStyle->getTheme(); QMenu* menu = ribbonBar()->addMenu(tr("Theme Options")); QAction* actionStyle = menu->addAction(tr(attribStyle)); QMenu* menuStyle = new QMenu(ribbonBar()); QActionGroup* styleActions = new QActionGroup(this); QAction* actionBlue = menuStyle->addAction(tr("Office 2007 Blue")); actionBlue->setObjectName("Office2007Blue"); actionBlue->setChecked(themeId == Qtitan::RibbonStyle::Office2007Blue); QAction* actionBlack = menuStyle->addAction(tr("Office 2007 Black")); actionBlack->setObjectName("Office2007Black"); actionBlack->setChecked(themeId == Qtitan::RibbonStyle::Office2007Black); QAction* actionSilver = menuStyle->addAction(tr("Office 2007 Silver")); actionSilver->setObjectName("Office2007Silver"); actionSilver->setChecked(themeId == Qtitan::RibbonStyle::Office2007Silver); QAction* actionAqua = menuStyle->addAction(tr("Office 2007 Aqua")); actionAqua->setObjectName("Office2007Aqua"); actionAqua->setChecked(themeId == Qtitan::RibbonStyle::Office2007Aqua); QAction* actionScenic = menuStyle->addAction(tr("Windows 7 Scenic")); actionScenic->setObjectName("Windows7Scenic"); actionScenic->setChecked(themeId == Qtitan::RibbonStyle::Windows7Scenic); QAction* action2010Blue = menuStyle->addAction(tr("Office 2010 Blue")); action2010Blue->setObjectName("Office2010Blue"); action2010Blue->setChecked(themeId == Qtitan::RibbonStyle::Office2010Blue); QAction* action2010Silver = menuStyle->addAction(tr("Office 2010 Silver")); action2010Silver->setObjectName("Office2010Silver"); action2010Silver->setChecked(themeId == Qtitan::RibbonStyle::Office2010Silver); QAction* action2010Black = menuStyle->addAction(tr("Office 2010 Black")); action2010Black->setObjectName("Office2010Black"); action2010Black->setChecked(themeId == Qtitan::RibbonStyle::Office2010Black); QAction* action2013White = menuStyle->addAction(tr("Office 2013 White")); action2013White->setObjectName("Office2013White"); action2013White->setChecked(themeId == Qtitan::RibbonStyle::Office2013White); QAction* action2013Gray = menuStyle->addAction(tr("Office 2013 Gray")); action2013Gray->setObjectName("Office2013Gray"); action2013Gray->setChecked(themeId == Qtitan::RibbonStyle::Office2013Gray); styleActions->addAction(actionBlue); styleActions->addAction(actionBlack); styleActions->addAction(actionSilver); styleActions->addAction(actionAqua); styleActions->addAction(actionScenic); styleActions->addAction(action2010Blue); styleActions->addAction(action2010Silver); styleActions->addAction(action2010Black); styleActions->addAction(action2013White); styleActions->addAction(action2013Gray); styleActions->setEnabled(true); QList styleActionsList= styleActions->actions(); for(int i = 0 ; i < styleActionsList.size(); i++) { styleActionsList.at(i)->setCheckable(true); } actionStyle->setMenu(menuStyle); connect(styleActions, SIGNAL(triggered(QAction*)), this, SLOT(handleStyleChange(QAction*))); } void RobloxRibbonMainWindow::setTheme(Qtitan::RibbonStyle::Theme themeId) { if (!m_pRibbonStyle) return; if (QToolButton* pButton = ribbonBar()->getSystemButton()) { if (themeId == Qtitan::OfficeStyle::Windows7Scenic || themeId == Qtitan::OfficeStyle::Office2010Blue || themeId == Qtitan::OfficeStyle::Office2010Silver || themeId == Qtitan::OfficeStyle::Office2010Black || themeId == Qtitan::OfficeStyle::Office2013White || themeId == Qtitan::OfficeStyle::Office2013Gray) { pButton->setToolButtonStyle(Qt::ToolButtonTextOnly); } else { pButton->setToolButtonStyle(Qt::ToolButtonFollowStyle); } } m_pRibbonStyle->setTheme(themeId); } QAction* RobloxRibbonMainWindow::findChildAction(const QString& actionName) const { return findChild(actionName); } void RobloxRibbonMainWindow::populateNameValueStore(const QString& storeID, const QObject *pObject) { //first clear the store NameValueStoreManager::singleton().clear(storeID); if (!pObject || !pObject->dynamicPropertyNames().count()) return; //now we are ready to populate NameValueStoreManager::singleton().setValue(storeID, "objectName", pObject->objectName()); //set the dynamic properties for later use for (int ii = 0; ii < pObject->dynamicPropertyNames().count(); ++ii) { QString propName = pObject->dynamicPropertyNames().at(ii); if (!propName.isEmpty()) NameValueStoreManager::singleton().setValue(storeID, propName, pObject->property(qPrintable(propName))); } } QString RobloxRibbonMainWindow::getFullName(QObject* pObject) { QString fullName; while (pObject && (pObject != this)) { if (!fullName.isEmpty()) fullName.append('.'); fullName.append(pObject->objectName()); pObject = pObject->parent(); } return fullName; }