/**************************************************************************** ** ** Copyright (C) 2016 Digia Plc and/or its subsidiary(-ies). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "externaltoolmanager.h" #include "externaltool.h" #include "coreconstants.h" #include "icontext.h" #include "icore.h" #include "messagemanager.h" #include "actionmanager/actionmanager.h" #include "actionmanager/actioncontainer.h" #include "actionmanager/command.h" #include #include #include #include #include using namespace Core::Internal; using namespace Utils; namespace Core { const char kSpecialUncategorizedSetting[] = "SpecialEmptyCategoryForUncategorizedTools"; struct ExternalToolManagerPrivate { QMap m_tools; QMap > m_categoryMap; QMap m_actions; QMap m_containers; QAction *m_configureSeparator; QAction *m_configureAction; }; static ExternalToolManager *m_instance = nullptr; static ExternalToolManagerPrivate *d = nullptr; static void writeSettings(); static void readSettings(const QMap &tools, QMap > *categoryPriorityMap); static void parseDirectory(const QString &directory, QMap > *categoryMenus, QMap *tools, bool isPreset = false); ExternalToolManager::ExternalToolManager() : QObject(ICore::instance()) { m_instance = this; d = new ExternalToolManagerPrivate; d->m_configureSeparator = new QAction(this); d->m_configureSeparator->setSeparator(true); d->m_configureAction = new QAction(ICore::msgShowOptionsDialog(), this); connect(d->m_configureAction, &QAction::triggered, this, [] { ICore::showOptionsDialog(Constants::SETTINGS_ID_TOOLS); }); // add the external tools menu ActionContainer *mexternaltools = ActionManager::createMenu(Id(Constants::M_TOOLS_EXTERNAL)); mexternaltools->menu()->setTitle(ExternalToolManager::tr("&External")); ActionContainer *mtools = ActionManager::actionContainer(Constants::M_TOOLS); mtools->addMenu(mexternaltools, Constants::G_DEFAULT_THREE); QMap > categoryPriorityMap; QMap tools; parseDirectory(ICore::userResourcePath() + QLatin1String("/externaltools"), &categoryPriorityMap, &tools); parseDirectory(ICore::resourcePath() + QLatin1String("/externaltools"), &categoryPriorityMap, &tools, true); QMap > categoryMap; for (auto it = categoryPriorityMap.cbegin(), end = categoryPriorityMap.cend(); it != end; ++it) categoryMap.insert(it.key(), it.value().values()); // read renamed categories and custom order readSettings(tools, &categoryMap); setToolsByCategory(categoryMap); } ExternalToolManager::~ExternalToolManager() { writeSettings(); // TODO kill running tools qDeleteAll(d->m_tools); delete d; } ExternalToolManager *ExternalToolManager::instance() { return m_instance; } static void parseDirectory(const QString &directory, QMap > *categoryMenus, QMap *tools, bool isPreset) { QTC_ASSERT(categoryMenus, return); QTC_ASSERT(tools, return); QDir dir(directory, QLatin1String("*.xml"), QDir::Unsorted, QDir::Files | QDir::Readable); foreach (const QFileInfo &info, dir.entryInfoList()) { const QString &fileName = info.absoluteFilePath(); QString error; ExternalTool *tool = ExternalTool::createFromFile(fileName, &error, ICore::userInterfaceLanguage()); if (!tool) { qWarning() << ExternalTool::tr("Error while parsing external tool %1: %2").arg(fileName, error); continue; } if (tools->contains(tool->id())) { if (isPreset) { // preset that was changed ExternalTool *other = tools->value(tool->id()); other->setPreset(QSharedPointer(tool)); } else { qWarning() << ExternalToolManager::tr("Error: External tool in %1 has duplicate id").arg(fileName); delete tool; } continue; } if (isPreset) { // preset that wasn't changed --> save original values tool->setPreset(QSharedPointer(new ExternalTool(tool))); } tools->insert(tool->id(), tool); (*categoryMenus)[tool->displayCategory()].insert(tool->order(), tool); } } QMap > ExternalToolManager::toolsByCategory() { return d->m_categoryMap; } QMap ExternalToolManager::toolsById() { return d->m_tools; } void ExternalToolManager::setToolsByCategory(const QMap > &tools) { // clear menu ActionContainer *mexternaltools = ActionManager::actionContainer(Id(Constants::M_TOOLS_EXTERNAL)); mexternaltools->clear(); // delete old tools and create list of new ones QMap newTools; QMap newActions; for (auto it = tools.cbegin(), end = tools.cend(); it != end; ++it) { foreach (ExternalTool *tool, it.value()) { const QString id = tool->id(); if (d->m_tools.value(id) == tool) { newActions.insert(id, d->m_actions.value(id)); // remove from list to prevent deletion d->m_tools.remove(id); d->m_actions.remove(id); } newTools.insert(id, tool); } } qDeleteAll(d->m_tools); const Id externalToolsPrefix = "Tools.External."; for (auto remainingActions = d->m_actions.cbegin(), end = d->m_actions.cend(); remainingActions != end; ++remainingActions) { ActionManager::unregisterAction(remainingActions.value(), externalToolsPrefix.withSuffix(remainingActions.key())); delete remainingActions.value(); } d->m_actions.clear(); // assign the new stuff d->m_tools = newTools; d->m_actions = newActions; d->m_categoryMap = tools; // create menu structure and remove no-longer used containers // add all the category menus, QMap is nicely sorted QMap newContainers; for (auto it = tools.cbegin(), end = tools.cend(); it != end; ++it) { ActionContainer *container = nullptr; const QString &containerName = it.key(); if (containerName.isEmpty()) { // no displayCategory, so put into external tools menu directly container = mexternaltools; } else { if (d->m_containers.contains(containerName)) container = d->m_containers.take(containerName); // remove to avoid deletion below else container = ActionManager::createMenu(Id("Tools.External.Category.").withSuffix(containerName)); newContainers.insert(containerName, container); mexternaltools->addMenu(container, Constants::G_DEFAULT_ONE); container->menu()->setTitle(containerName); } foreach (ExternalTool *tool, it.value()) { const QString &toolId = tool->id(); // tool action and command QAction *action = nullptr; Command *command = nullptr; if (d->m_actions.contains(toolId)) { action = d->m_actions.value(toolId); command = ActionManager::command(externalToolsPrefix.withSuffix(toolId)); } else { action = new QAction(tool->displayName(), m_instance); d->m_actions.insert(toolId, action); connect(action, &QAction::triggered, tool, [tool] { auto runner = new ExternalToolRunner(tool); if (runner->hasError()) MessageManager::write(runner->errorString()); }); command = ActionManager::registerAction(action, externalToolsPrefix.withSuffix(toolId)); command->setAttribute(Command::CA_UpdateText); } action->setText(tool->displayName()); action->setToolTip(tool->description()); action->setWhatsThis(tool->description()); container->addAction(command, Constants::G_DEFAULT_TWO); } } // delete the unused containers qDeleteAll(d->m_containers); // remember the new containers d->m_containers = newContainers; // (re)add the configure menu item mexternaltools->menu()->addAction(d->m_configureSeparator); mexternaltools->menu()->addAction(d->m_configureAction); } static void readSettings(const QMap &tools, QMap > *categoryMap) { QSettings *settings = ICore::settings(); settings->beginGroup(QLatin1String("ExternalTools")); if (categoryMap) { settings->beginGroup(QLatin1String("OverrideCategories")); foreach (const QString &settingsCategory, settings->childGroups()) { QString displayCategory = settingsCategory; if (displayCategory == QLatin1String(kSpecialUncategorizedSetting)) displayCategory = QLatin1String(""); int count = settings->beginReadArray(settingsCategory); for (int i = 0; i < count; ++i) { settings->setArrayIndex(i); const QString &toolId = settings->value(QLatin1String("Tool")).toString(); if (tools.contains(toolId)) { ExternalTool *tool = tools.value(toolId); // remove from old category (*categoryMap)[tool->displayCategory()].removeAll(tool); if (categoryMap->value(tool->displayCategory()).isEmpty()) categoryMap->remove(tool->displayCategory()); // add to new category (*categoryMap)[displayCategory].append(tool); } } settings->endArray(); } settings->endGroup(); } settings->endGroup(); } static void writeSettings() { QSettings *settings = ICore::settings(); settings->beginGroup(QLatin1String("ExternalTools")); settings->remove(QLatin1String("")); settings->beginGroup(QLatin1String("OverrideCategories")); for (auto it = d->m_categoryMap.cbegin(), end = d->m_categoryMap.cend(); it != end; ++it) { QString category = it.key(); if (category.isEmpty()) category = QLatin1String(kSpecialUncategorizedSetting); settings->beginWriteArray(category, it.value().count()); int i = 0; foreach (ExternalTool *tool, it.value()) { settings->setArrayIndex(i); settings->setValue(QLatin1String("Tool"), tool->id()); ++i; } settings->endArray(); } settings->endGroup(); settings->endGroup(); } void ExternalToolManager::emitReplaceSelectionRequested(const QString &output) { emit m_instance->replaceSelectionRequested(output); } } // namespace Core