diff --git a/kdevplatform/shell/areadisplay.cpp b/kdevplatform/shell/areadisplay.cpp index 2573d464f2..a0e41287e7 100644 --- a/kdevplatform/shell/areadisplay.cpp +++ b/kdevplatform/shell/areadisplay.cpp @@ -1,125 +1,125 @@ /*************************************************************************** * Copyright 2013 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "areadisplay.h" #include "mainwindow.h" #include "workingsetcontroller.h" #include "core.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; AreaDisplay::AreaDisplay(KDevelop::MainWindow* parent) : QWidget(parent) , m_mainWindow(parent) { setLayout(new QHBoxLayout); m_separator = new QLabel(QStringLiteral("|"), this); m_separator->setEnabled(false); m_separator->setVisible(false); layout()->addWidget(m_separator); layout()->setContentsMargins(0, 0, 0, 0); layout()->addWidget(Core::self()->workingSetControllerInternal()->createSetManagerWidget(m_mainWindow)); m_button = new QToolButton(this); m_button->setToolTip(i18n( "Execute actions to change the area.
" "An area is a tool view configuration for a specific use case. " "From here you can also navigate back to the default code area.")); m_button->setAutoRaise(true); m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_button->setPopupMode(QToolButton::InstantPopup); layout()->addWidget(m_button); connect(parent, &MainWindow::areaChanged, this, &AreaDisplay::newArea); } void AreaDisplay::newArea(Sublime::Area* area) { if(m_button->menu()) m_button->menu()->deleteLater(); Sublime::Area* currentArea = m_mainWindow->area(); m_button->setText(currentArea->title()); m_button->setIcon(QIcon::fromTheme(currentArea->iconName())); auto* m = new QMenu(m_button); m->addActions(area->actions()); if (currentArea->objectName() != QLatin1String("code")) { if(!m->actions().isEmpty()) m->addSeparator(); - m->addAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Back to code"), this, SLOT(backToCode()), QKeySequence(Qt::AltModifier | Qt::Key_Backspace)); + m->addAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18nc("@action:inmenu", "Back to Code"), this, SLOT(backToCode()), QKeySequence(Qt::AltModifier | Qt::Key_Backspace)); } m_button->setMenu(m); //remove the additional widgets we might have added for the last area auto* l = qobject_cast(layout()); if(l->count()>=4) { QLayoutItem* item = l->takeAt(0); delete item->widget(); delete item; } QWidget* w = Core::self()->workingSetControllerInternal()->createSetManagerWidget(m_mainWindow, area); w->installEventFilter(this); m_separator->setVisible(w->isVisible()); l->insertWidget(0, w); } bool AreaDisplay::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Show) { m_separator->setVisible(true); } else if (event->type() == QEvent::Hide) { m_separator->setVisible(false); } return QObject::eventFilter(obj, event); } void AreaDisplay::backToCode() { ICore::self()->uiController()->switchToArea(QStringLiteral("code"), IUiController::ThisWindow); } QSize AreaDisplay::minimumSizeHint() const { QSize hint = QWidget::minimumSizeHint(); hint = hint.boundedTo(QSize(hint.width(), m_mainWindow->menuBar()->height()-1)); return hint; } QSize AreaDisplay::sizeHint() const { QSize hint = QWidget::sizeHint(); hint = hint.boundedTo(QSize(hint.width(), m_mainWindow->menuBar()->height()-1)); return hint; } diff --git a/kdevplatform/shell/configdialog.cpp b/kdevplatform/shell/configdialog.cpp index 28598b5866..7dff72ca75 100644 --- a/kdevplatform/shell/configdialog.cpp +++ b/kdevplatform/shell/configdialog.cpp @@ -1,217 +1,217 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "configdialog.h" #include "debug.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; //FIXME: unit test this code! ConfigDialog::ConfigDialog(QWidget* parent) : KPageDialog(parent) { - setWindowTitle(i18n("Configure")); + setWindowTitle(i18nc("@title:window", "Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Apply)->setEnabled(false); setObjectName(QStringLiteral("configdialog")); auto onApplyClicked = [this] { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); applyChanges(page); }; connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, onApplyClicked); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, [this]() { auto page = qobject_cast(currentPage()->widget()); Q_ASSERT(page); page->defaults(); }); connect(this, &KPageDialog::currentPageChanged, this, &ConfigDialog::checkForUnsavedChanges); // make sure we don't keep any entries for unloaded plugins connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ConfigDialog::removePagesForPlugin); } KPageWidgetItem* ConfigDialog::itemForPage(ConfigPage* page) const { for (auto& item : m_pages) { if (item->widget() == page) { return item; } } return nullptr; } int ConfigDialog::checkForUnsavedChanges(KPageWidgetItem* current, KPageWidgetItem* before) { Q_UNUSED(current); if (!m_currentPageHasChanges) { return KMessageBox::Yes; } // before must be non-null, because if we change from nothing to a new page m_currentPageHasChanges must also be false! Q_ASSERT(before); auto oldPage = qobject_cast(before->widget()); Q_ASSERT(oldPage); auto dialogResult = KMessageBox::warningYesNoCancel(this, i18n("The settings of the current module have changed.\n" - "Do you want to apply the changes or discard them?"), i18n("Apply Settings"), KStandardGuiItem::apply(), + "Do you want to apply the changes or discard them?"), i18nc("@title:window", "Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (dialogResult == KMessageBox::No) { oldPage->reset(); m_currentPageHasChanges = false; button(QDialogButtonBox::Apply)->setEnabled(false); } else if (dialogResult == KMessageBox::Yes) { applyChanges(oldPage); } else if (dialogResult == KMessageBox::Cancel) { // restore old state QSignalBlocker block(this); // prevent recursion setCurrentPage(before); } return dialogResult; } void ConfigDialog::closeEvent(QCloseEvent* event) { if (checkForUnsavedChanges(currentPage(), currentPage()) == KMessageBox::Cancel) { // if the user clicked cancel he wants to continue editing the current page -> don't close event->ignore(); } else { event->accept(); } } void ConfigDialog::removeConfigPage(ConfigPage* page) { auto item = itemForPage(page); Q_ASSERT(item); removePage(item); m_pages.removeAll(QPointer(item)); // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::removePagesForPlugin(IPlugin* plugin) { Q_ASSERT(plugin); const auto oldPages = m_pages; for (auto&& item : oldPages) { if (!item) { continue; } auto page = qobject_cast(item->widget()); if (page && page->plugin() == plugin) { removePage(item); // this also deletes the config page -> QPointer is set to null } }; // also remove all items that were deleted because a parent KPageWidgetItem was removed m_pages.removeAll(QPointer()); } void ConfigDialog::appendConfigPage(ConfigPage* page) { addConfigPageInternal(addPage(page, page->name()), page); } void ConfigDialog::insertConfigPage(ConfigPage* before, ConfigPage* page) { Q_ASSERT(before); auto beforeItem = itemForPage(before); Q_ASSERT(beforeItem); addConfigPageInternal(insertPage(beforeItem, page, page->name()), page); } void ConfigDialog::appendSubConfigPage(ConfigPage* parentPage, ConfigPage* page) { auto item = itemForPage(parentPage); Q_ASSERT(item); addConfigPageInternal(addSubPage(item, page, page->name()), page); } void ConfigDialog::addConfigPageInternal(KPageWidgetItem* item, ConfigPage* page) { item->setHeader(page->fullName()); item->setIcon(page->icon()); page->initConfigManager(); page->reset(); // make sure all widgets are in the correct state // make sure that we only connect to changed after calling reset() connect(page, &ConfigPage::changed, this, &ConfigDialog::onPageChanged); m_pages.append(item); for (int i = 0; i < page->childPages(); ++i) { auto child = page->childPage(i); appendSubConfigPage(page, child); } } void ConfigDialog::onPageChanged() { QObject* from = sender(); if (from && from != currentPage()->widget()) { qCWarning(SHELL) << "Settings in config page" << from << "changed, while" << currentPage()->widget() << "is currently selected. This case is not implemented yet."; return; // TODO: add a QHash as a member to make sure the apply button is always correct // TODO: when pressing okay show confirm dialog if other pages have changed or just silently apply every page? "Items on other pages have changed, do you wish to review those changes? + list with changed pages." } if (!m_currentlyApplyingChanges) { // e.g. PluginPreferences emits changed() from its apply method, better fix this here than having to // ensure that no plugin emits changed() from apply() // together with KPageDialog emitting currentPageChanged("Plugins", nullptr) this could cause a crash // when we dereference before m_currentPageHasChanges = true; button(QDialogButtonBox::Apply)->setEnabled(true); } } void ConfigDialog::applyChanges(ConfigPage* page) { // must set this to false before calling apply, otherwise we get the confirmation dialog // whenever we enable/disable plugins. // This is because KPageWidget then emits currentPageChanged("Plugins", nullptr), which seems like a bug to me, // it should rather emit currentPageChanged("Plugins", "Plugins") or even better nothing at all, since the current // page did not actually change! // TODO: fix KPageWidget m_currentPageHasChanges = false; m_currentlyApplyingChanges = true; page->apply(); m_currentlyApplyingChanges = false; Q_ASSERT(!m_currentPageHasChanges); button(QDialogButtonBox::Apply)->setEnabled(false); emit configSaved(page); } diff --git a/kdevplatform/shell/core.cpp b/kdevplatform/shell/core.cpp index 772aecf94a..d3dd4f0d43 100644 --- a/kdevplatform/shell/core.cpp +++ b/kdevplatform/shell/core.cpp @@ -1,576 +1,576 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2007 Kris Wong * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "core.h" #include "core_p.h" #include #include #include #include #include "mainwindow.h" #include "sessioncontroller.h" #include "uicontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "partcontroller.h" #include "languagecontroller.h" #include "documentcontroller.h" #include "runcontroller.h" #include "session.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "progresswidget/progressmanager.h" #include "selectioncontroller.h" #include "debugcontroller.h" #include "kdevplatform_version.h" #include "workingsetcontroller.h" #include "testcontroller.h" #include "runtimecontroller.h" #include "debug.h" #include namespace { void shutdownGracefully(int sig) { static volatile std::sig_atomic_t handlingSignal = 0; if ( !handlingSignal ) { handlingSignal = 1; qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully"; QCoreApplication* app = QCoreApplication::instance(); if (auto* guiApp = qobject_cast(app)) { guiApp->closeAllWindows(); } app->quit(); return; } // re-raise signal with default handler and trigger program termination std::signal(sig, SIG_DFL); std::raise(sig); } void installSignalHandler() { #ifdef SIGHUP std::signal(SIGHUP, shutdownGracefully); #endif #ifdef SIGINT std::signal(SIGINT, shutdownGracefully); #endif #ifdef SIGTERM std::signal(SIGTERM, shutdownGracefully); #endif } } namespace KDevelop { Core *Core::m_self = nullptr; CorePrivate::CorePrivate(Core *core) : m_core(core) , m_cleanedUp(false) , m_shuttingDown(false) { } bool CorePrivate::initialize(Core::Setup mode, const QString& session ) { m_mode=mode; qCDebug(SHELL) << "Creating controllers"; if( !sessionController ) { sessionController = new SessionController(m_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(); } qCDebug(SHELL) << "Creating ui controller"; if( !uiController ) { uiController = new UiController(m_core); } qCDebug(SHELL) << "Creating plugin controller"; if( !pluginController ) { pluginController = new PluginController(m_core); const auto pluginInfos = pluginController->allPluginInfos(); if (pluginInfos.isEmpty()) { QMessageBox::critical(nullptr, - i18n("Could not find any plugins"), + i18nc("@title:window", "No Plugins Found"), i18n("

Could not find any plugins during startup.
" "Please make sure QT_PLUGIN_PATH is set correctly.

" "Refer to this article for more information."), QMessageBox::Abort, QMessageBox::Abort); qCWarning(SHELL) << "Could not find any plugins, aborting"; return false; } } if( !partController && !(mode & Core::NoUi)) { partController = new PartController(m_core, uiController->defaultMainWindow()); } if( !projectController ) { projectController = new ProjectController(m_core); } if( !documentController ) { documentController = new DocumentController(m_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. languageController = new LanguageController(m_core); } if( !runController ) { runController = new RunController(m_core); } if( !sourceFormatterController ) { sourceFormatterController = new SourceFormatterController(m_core); } if ( !progressController) { progressController = ProgressManager::instance(); } if( !selectionController ) { selectionController = new SelectionController(m_core); } if( !documentationController && !(mode & Core::NoUi) ) { documentationController = new DocumentationController(m_core); } if( !runtimeController ) { runtimeController = new RuntimeController(m_core); } if( !debugController ) { debugController = new DebugController(m_core); } if( !testController ) { testController = new TestController(m_core); } qCDebug(SHELL) << "Done creating controllers"; qCDebug(SHELL) << "Initializing controllers"; sessionController->initialize( session ); if( !sessionController->activeSessionLock() ) { return false; } // TODO: Is this early enough, or should we put the loading of the session into // the controller construct DUChain::initialize(); if (!(mode & Core::NoUi)) { uiController->initialize(); } languageController->initialize(); languageController->backgroundParser()->suspend(); // eventually resume the background parser once the project controller // has been initialized. At that point we know whether there are projects loading // which the background parser is handling internally to defer parse jobs QObject::connect(projectController.data(), &ProjectController::initialized, m_core, [this]() { languageController->backgroundParser()->resume(); }); if (partController) { partController->initialize(); } projectController->initialize(); documentController->initialize(); /* This is somewhat messy. We want to load the areas before loading the plugins, so that when each plugin is loaded we know if an area wants some of the tool view from that plugin. OTOH, loading of areas creates documents, and some documents might require that a plugin is already loaded. Probably, the best approach would be to plugins to just add tool views to a list of available tool view, and then grab those tool views when loading an area. */ qCDebug(SHELL) << "Initializing plugin controller (loading session plugins)"; pluginController->initialize(); qCDebug(SHELL) << "Initializing working set controller"; if(!(mode & Core::NoUi)) { workingSetController->initialize(); /* Need to do this after everything else is loaded. It's too hard to restore position of views, and toolbars, and whatever that are not created yet. */ uiController->loadAllAreas(KSharedConfig::openConfig()); uiController->defaultMainWindow()->show(); } qCDebug(SHELL) << "Initializing remaining controllers"; runController->initialize(); sourceFormatterController->initialize(); selectionController->initialize(); if (documentationController) { documentationController->initialize(); } debugController->initialize(); testController->initialize(); runtimeController->initialize(); installSignalHandler(); qCDebug(SHELL) << "Done initializing controllers"; return true; } CorePrivate::~CorePrivate() { delete selectionController.data(); delete projectController.data(); delete languageController.data(); delete pluginController.data(); delete uiController.data(); delete partController.data(); delete documentController.data(); delete runController.data(); delete sessionController.data(); delete sourceFormatterController.data(); delete documentationController.data(); delete debugController.data(); delete workingSetController.data(); delete testController.data(); delete runtimeController.data(); selectionController.clear(); projectController.clear(); languageController.clear(); pluginController.clear(); uiController.clear(); partController.clear(); documentController.clear(); runController.clear(); sessionController.clear(); sourceFormatterController.clear(); documentationController.clear(); debugController.clear(); workingSetController.clear(); testController.clear(); runtimeController.clear(); } bool Core::initialize(Setup mode, const QString& session) { if (m_self) return true; m_self = new Core(); bool ret = m_self->d->initialize(mode, session); if(ret) emit m_self->initializationCompleted(); return ret; } Core *KDevelop::Core::self() { return m_self; } Core::Core(QObject *parent) : ICore(parent) { d = new CorePrivate(this); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::Core(CorePrivate* dd, QObject* parent) : ICore(parent), d(dd) { connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Core::shutdown); } Core::~Core() { qCDebug(SHELL); //Cleanup already called before mass destruction of GUI delete d; m_self = nullptr; } Core::Setup Core::setupFlags() const { return d->m_mode; } void Core::shutdown() { qCDebug(SHELL); if (!d->m_shuttingDown) { cleanup(); deleteLater(); } qCDebug(SHELL) << "Shutdown done"; } bool Core::shuttingDown() const { return d->m_shuttingDown; } void Core::cleanup() { qCDebug(SHELL); d->m_shuttingDown = true; emit aboutToShutdown(); if (!d->m_cleanedUp) { // first of all: request stop of all background parser jobs d->languageController->backgroundParser()->abortAllJobs(); d->languageController->backgroundParser()->suspend(); d->debugController->cleanup(); d->selectionController->cleanup(); // Save the layout of the ui here, so run it first d->uiController->cleanup(); if (d->workingSetController) d->workingSetController->cleanup(); /* Must be called before projectController->cleanup(). */ // Closes all documents (discards, as already saved if the user wished earlier) d->documentController->cleanup(); d->runController->cleanup(); if (d->partController) { d->partController->cleanup(); } d->projectController->cleanup(); d->sourceFormatterController->cleanup(); // before unloading language plugins, we need to make sure all parse jobs are done d->languageController->backgroundParser()->waitForIdle(); DUChain::self()->shutdown(); // Only unload plugins after the DUChain shutdown to prevent issues with non-loaded factories for types // See: https://bugs.kde.org/show_bug.cgi?id=379669 d->pluginController->cleanup(); d->sessionController->cleanup(); d->testController->cleanup(); //Disable the functionality of the language controller d->languageController->cleanup(); } d->m_cleanedUp = true; emit shutdownCompleted(); } IUiController *Core::uiController() { return d->uiController.data(); } ISession* Core::activeSession() { return sessionController()->activeSession(); } ISessionLock::Ptr Core::activeSessionLock() { return sessionController()->activeSessionLock(); } SessionController *Core::sessionController() { return d->sessionController.data(); } UiController *Core::uiControllerInternal() { return d->uiController.data(); } IPluginController *Core::pluginController() { return d->pluginController.data(); } PluginController *Core::pluginControllerInternal() { return d->pluginController.data(); } IProjectController *Core::projectController() { return d->projectController.data(); } ProjectController *Core::projectControllerInternal() { return d->projectController.data(); } IPartController *Core::partController() { return d->partController.data(); } PartController *Core::partControllerInternal() { return d->partController.data(); } ILanguageController *Core::languageController() { return d->languageController.data(); } LanguageController *Core::languageControllerInternal() { return d->languageController.data(); } IDocumentController *Core::documentController() { return d->documentController.data(); } DocumentController *Core::documentControllerInternal() { return d->documentController.data(); } IRunController *Core::runController() { return d->runController.data(); } RunController *Core::runControllerInternal() { return d->runController.data(); } ISourceFormatterController* Core::sourceFormatterController() { return d->sourceFormatterController.data(); } SourceFormatterController* Core::sourceFormatterControllerInternal() { return d->sourceFormatterController.data(); } ProgressManager *Core::progressController() { return d->progressController.data(); } ISelectionController* Core::selectionController() { return d->selectionController.data(); } IDocumentationController* Core::documentationController() { return d->documentationController.data(); } DocumentationController* Core::documentationControllerInternal() { return d->documentationController.data(); } IRuntimeController* Core::runtimeController() { return d->runtimeController.data(); } RuntimeController* Core::runtimeControllerInternal() { return d->runtimeController.data(); } IDebugController* Core::debugController() { return d->debugController.data(); } DebugController* Core::debugControllerInternal() { return d->debugController.data(); } ITestController* Core::testController() { return d->testController.data(); } TestController* Core::testControllerInternal() { return d->testController.data(); } WorkingSetController* Core::workingSetControllerInternal() { return d->workingSetController.data(); } QString Core::version() { return QStringLiteral(KDEVPLATFORM_VERSION_STRING); } } diff --git a/kdevplatform/shell/debugcontroller.cpp b/kdevplatform/shell/debugcontroller.cpp index d67b68baee..2b4a4a98a2 100644 --- a/kdevplatform/shell/debugcontroller.cpp +++ b/kdevplatform/shell/debugcontroller.cpp @@ -1,577 +1,577 @@ /* This file is part of KDevelop * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "debugcontroller.h" #include #include #include #include #include #include #include "../interfaces/idocument.h" #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/ipartcontroller.h" #include "../interfaces/contextmenuextension.h" #include "../interfaces/context.h" #include "../language/interfaces/editorcontext.h" #include "../sublime/view.h" #include "../sublime/mainwindow.h" #include "../sublime/area.h" #include "../debugger/breakpoint/breakpointmodel.h" #include "../debugger/breakpoint/breakpointwidget.h" #include "../debugger/variable/variablewidget.h" #include "../debugger/framestack/framestackmodel.h" #include "../debugger/framestack/framestackwidget.h" #include "core.h" #include "debug.h" #include "uicontroller.h" #include "iruncontroller.h" namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} QWidget* create(QWidget *parent = nullptr) override { return new T(m_controller, parent); } QString id() const override { return m_id; } Qt::DockWidgetArea defaultPosition() const override { return m_defaultArea; } void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts work, but they don't need any toolbar. So, suppress toolbar action. */ QList toolBarActions( QWidget* viewWidget ) const override { Q_UNUSED(viewWidget); return QList(); } private: DebugController* const m_controller; const QString m_id; const Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)) { setComponentName(QStringLiteral("kdevdebugger"), i18n("Debugger")); setXMLFile(QStringLiteral("kdevdebuggershellui.rc")); } void DebugController::initialize() { m_breakpointModel->load(); } void DebugController::initializeUi() { if (m_uiInitialized) return; m_uiInitialized = true; if((Core::self()->setupFlags() & Core::NoUi)) return; setupActions(); ICore::self()->uiController()->addToolView( - i18n("Frame Stack"), + i18nc("@title:window", "Frame Stack"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.StackView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( - i18n("Breakpoints"), + i18nc("@title:window", "Breakpoints"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.BreakpointsView"), Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( - i18n("Variables"), + i18nc("@title:window", "Variables"), new DebuggerToolFactory( this, QStringLiteral("org.kdevelop.debugger.VariablesView"), Qt::LeftDockWidgetArea)); const auto parts = KDevelop::ICore::self()->partController()->parts(); for (KParts::Part* p : parts) { partAdded(p); } connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &DebugController::partAdded); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged(QStringLiteral("ended")); } void DebugController::cleanup() { if (m_currentSession) m_currentSession.data()->stopDebugger(); } DebugController::~DebugController() { } BreakpointModel* DebugController::breakpointModel() { return m_breakpointModel; } VariableCollection* DebugController::variableCollection() { return m_variableCollection; } void DebugController::partAdded(KParts::Part* part) { if (auto* doc = qobject_cast(part)) { auto* iface = qobject_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); QAction* action = m_continueDebugger = new QAction(this); setContinueStartsDebug(true); ac->addAction(QStringLiteral("debug_continue"), action); connect(action, &QAction::triggered, this, &DebugController::run); #if 0 m_restartDebugger = action = new QAction(QIcon::fromTheme("media-seek-backward"), i18n("&Restart"), this); action->setToolTip( i18n("Restart program") ); action->setWhatsThis( i18n("Restarts applications from the beginning.") ); action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger())); ac->addAction("debug_restart", action); #endif - m_interruptDebugger = action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), i18n("Interrupt"), this); - action->setToolTip( i18n("Interrupt application") ); - action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); + m_interruptDebugger = action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")), i18nc("@action", "Interrupt"), this); + action->setToolTip( i18nc("@info:tooltip", "Interrupt application") ); + action->setWhatsThis(i18nc("@info:whatsthis", "Interrupts the debugged process or current debugger command.")); connect(action, &QAction::triggered, this, &DebugController::interruptDebugger); ac->addAction(QStringLiteral("debug_pause"), action); - m_runToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18n("Run to &Cursor"), this); - action->setToolTip( i18n("Run to cursor") ); - action->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); + m_runToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18nc("@action", "Run to &Cursor"), this); + action->setToolTip( i18nc("@info:tooltip", "Run to cursor") ); + action->setWhatsThis(i18nc("@info:whatsthis", "Continues execution until the cursor position is reached.")); connect(action, &QAction::triggered, this, &DebugController::runToCursor); ac->addAction(QStringLiteral("debug_runtocursor"), action); - m_jumpToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18n("Set E&xecution Position to Cursor"), this); - action->setToolTip( i18n("Jump to cursor") ); - action->setWhatsThis(i18n("Continue execution from the current cursor position.")); + m_jumpToCursor = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18nc("@action", "Set E&xecution Position to Cursor"), this); + action->setToolTip( i18nc("@info:tooltip", "Jump to cursor") ); + action->setWhatsThis(i18nc("@info:whatsthis", "Continue execution from the current cursor position.")); connect(action, &QAction::triggered, this, &DebugController::jumpToCursor); ac->addAction(QStringLiteral("debug_jumptocursor"), action); - m_stepOver = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-over")), i18n("Step &Over"), this); + m_stepOver = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-over")), i18nc("@action", "Step &Over"), this); ac->setDefaultShortcut( action, Qt::Key_F10); - action->setToolTip( i18n("Step over the next line") ); - action->setWhatsThis( i18n("Executes one line of source in the current source file. " + action->setToolTip( i18nc("@info:tooltip", "Step over the next line") ); + action->setWhatsThis( i18nc("@info:whatsthis", "Executes one line of source in the current source file. " "If the source line is a call to a function the whole " "function is executed and the app will stop at the line " "following the function call.") ); connect(action, &QAction::triggered, this, &DebugController::stepOver); ac->addAction(QStringLiteral("debug_stepover"), action); - m_stepOverInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-instruction")), i18n("Step over Ins&truction"), this); - action->setToolTip( i18n("Step over instruction") ); - action->setWhatsThis(i18n("Steps over the next assembly instruction.")); + m_stepOverInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-instruction")), i18nc("@action", "Step over Ins&truction"), this); + action->setToolTip( i18nc("@info:tooltip", "Step over instruction") ); + action->setWhatsThis(i18nc("@info:whatsthis", "Steps over the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepOverInstruction); ac->addAction(QStringLiteral("debug_stepoverinst"), action); - m_stepInto = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into")), i18n("Step &Into"), this); + m_stepInto = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into")), i18nc("@action", "Step &Into"), this); ac->setDefaultShortcut( action, Qt::Key_F11); - action->setToolTip( i18n("Step into the next statement") ); - action->setWhatsThis( i18n("Executes exactly one line of source. If the source line " + action->setToolTip( i18nc("@info:tooltip", "Step into the next statement") ); + action->setWhatsThis( i18nc("@info:whatsthis", "Executes exactly one line of source. If the source line " "is a call to a function then execution will stop after " "the function has been entered.") ); connect(action, &QAction::triggered, this, &DebugController::stepInto); ac->addAction(QStringLiteral("debug_stepinto"), action); - m_stepIntoInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into-instruction")), i18n("Step into I&nstruction"), this); - action->setToolTip( i18n("Step into instruction") ); - action->setWhatsThis(i18n("Steps into the next assembly instruction.")); + m_stepIntoInstruction = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-into-instruction")), i18nc("@action", "Step into I&nstruction"), this); + action->setToolTip( i18nc("@info:tooltip", "Step into instruction") ); + action->setWhatsThis(i18nc("@info:whatsthis", "Steps into the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepIntoInstruction); ac->addAction(QStringLiteral("debug_stepintoinst"), action); - m_stepOut = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-out")), i18n("Step O&ut"), this); + m_stepOut = action = new QAction(QIcon::fromTheme(QStringLiteral("debug-step-out")), i18nc("@action", "Step O&ut"), this); ac->setDefaultShortcut( action, Qt::Key_F12); - action->setToolTip( i18n("Step out of the current function") ); - action->setWhatsThis( i18n("Executes the application until the currently executing " + action->setToolTip( i18nc("@info:tooltip", "Step out of the current function") ); + action->setWhatsThis( i18nc("@whatsthis", "Executes the application until the currently executing " "function is completed. The debugger will then display " "the line after the original call to that function. If " "program execution is in the outermost frame (i.e. in " "main()) then this operation has no effect.") ); connect(action, &QAction::triggered, this, &DebugController::stepOut); ac->addAction(QStringLiteral("debug_stepout"), action); - m_toggleBreakpoint = action = new QAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18n("Toggle Breakpoint"), this); + m_toggleBreakpoint = action = new QAction(QIcon::fromTheme(QStringLiteral("breakpoint")), i18nc("@action", "Toggle Breakpoint"), this); ac->setDefaultShortcut( action, i18n("Ctrl+Alt+B") ); - action->setToolTip(i18n("Toggle breakpoint")); - action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); + action->setToolTip(i18nc("@info:tooltip", "Toggle breakpoint")); + action->setWhatsThis(i18nc("@info:whatsthis", "Toggles the breakpoint at the current line in editor.")); connect(action, &QAction::triggered, this, &DebugController::toggleBreakpoint); ac->addAction(QStringLiteral("debug_toggle_breakpoint"), action); - m_showCurrentLine = action = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("Show Current Line"), this); - action->setToolTip(i18n("Show the current execution position")); - action->setWhatsThis(i18n("Jumps to the execution line in the editor.")); + m_showCurrentLine = action = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18nc("@action", "Show Current Line"), this); + action->setToolTip(i18nc("@info:tooltip", "Show the current execution position")); + action->setWhatsThis(i18nc("@info:whatsthis", "Jumps to the execution line in the editor.")); connect(action, &QAction::triggered, this, &DebugController::showCurrentLine); ac->addAction(QStringLiteral("debug_showcurrentline"), action); } void DebugController::addSession(IDebugSession* session) { qCDebug(SHELL) << session; Q_ASSERT(session->variableController()); Q_ASSERT(session->breakpointController()); Q_ASSERT(session->frameStackModel()); //TODO support multiple sessions if (m_currentSession) { m_currentSession.data()->stopDebugger(); } m_currentSession = session; connect(session, &IDebugSession::stateChanged, this, &DebugController::debuggerStateChanged); connect(session, &IDebugSession::showStepInSource, this, &DebugController::showStepInSource); connect(session, &IDebugSession::clearExecutionPoint, this, &DebugController::clearExecutionPoint); connect(session, &IDebugSession::raiseFramestackViews, this, &DebugController::raiseFramestackViews); updateDebuggerState(session->state(), session); emit currentSessionChanged(session); if((Core::self()->setupFlags() & Core::NoUi)) return; Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow->area()->objectName() != QLatin1String("debug")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("debug"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, &Sublime::MainWindow::areaChanged, this, &DebugController::areaChanged); } } void DebugController::clearExecutionPoint() { qCDebug(SHELL); const auto documents = KDevelop::ICore::self()->documentController()->openDocuments(); for (KDevelop::IDocument* document : documents) { auto* iface = qobject_cast(document->textDocument()); if (!iface) continue; const auto oldMarks = iface->marks(); for (KTextEditor::Mark* mark : oldMarks) { if (mark->type & KTextEditor::MarkInterface::Execution) { iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution ); } } } } void DebugController::showStepInSource(const QUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); qCDebug(SHELL) << url << lineNum; Q_ASSERT(qobject_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( url, lineNum )); KDevelop::IDocument* document = KDevelop::ICore::self() ->documentController() ->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus); if( !document ) return; auto* iface = qobject_cast(document->textDocument()); if( !iface ) return; { QSignalBlocker blocker(document->textDocument()); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); } } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(qobject_cast(sender())); auto* session = static_cast(sender()); qCDebug(SHELL) << session << state << "current" << m_currentSession.data(); if (session == m_currentSession.data()) { updateDebuggerState(state, session); } if (state == IDebugSession::EndedState) { if (session == m_currentSession.data()) { m_currentSession.clear(); emit currentSessionChanged(nullptr); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != QLatin1String("code")) { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea(QStringLiteral("code"), IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } - ICore::self()->uiController()->findToolView(i18n("Debug"), nullptr, IUiController::Raise); + ICore::self()->uiController()->findToolView(i18nc("@title:window", "Debug"), nullptr, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; qCDebug(SHELL) << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: qCDebug(SHELL) << "new state: stopped"; stateChanged(QStringLiteral("stopped")); setContinueStartsDebug(true); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: qCDebug(SHELL) << "new state: paused"; stateChanged(QStringLiteral("paused")); setContinueStartsDebug(false); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: qCDebug(SHELL) << "new state: active"; stateChanged(QStringLiteral("active")); setContinueStartsDebug(false); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: qCDebug(SHELL) << "new state: ended"; stateChanged(QStringLiteral("ended")); setContinueStartsDebug(true); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } void DebugController::setContinueStartsDebug(bool startsDebug) { if (startsDebug) { - m_continueDebugger->setText(i18n("Debug Launch")); + m_continueDebugger->setText(i18nc("@action", "Debug Launch")); m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("debug-run"))); - m_continueDebugger->setToolTip(i18n("Debug current launch")); - m_continueDebugger->setWhatsThis(i18n("Executes the target or the program specified in " + m_continueDebugger->setToolTip(i18nc("@info:tooltip", "Debug current launch")); + m_continueDebugger->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in " "currently active launch configuration inside a Debugger.")); } else { - m_continueDebugger->setText(i18n("&Continue")); + m_continueDebugger->setText(i18nc("@action", "&Continue")); m_continueDebugger->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); - m_continueDebugger->setToolTip(i18n("Continue application execution") ); - m_continueDebugger->setWhatsThis(i18n("Continues the execution of your application in the " + m_continueDebugger->setToolTip(i18nc("@info:tooltip", "Continue application execution") ); + m_continueDebugger->setWhatsThis(i18nc("@info:whatsthis", "Continues the execution of your application in the " "debugger. This only takes effect when the application " "has been halted by the debugger (i.e. a breakpoint has " "been activated or the interrupt was pressed).") ); } } ContextMenuExtension DebugController::contextMenuExtension(Context* context, QWidget* parent) { Q_UNUSED(parent); ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; auto *econtext = dynamic_cast(context); if (!econtext) return menuExt; if (m_currentSession && m_currentSession.data()->isRunning()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor); } if (econtext->url().isLocalFile()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint); } return menuExt; } #if 0 void DebugController::restartDebugger() { if (m_currentSession) { m_currentSession.data()->restartDebugger(); } } #endif void DebugController::stopDebugger() { if (m_currentSession) { m_currentSession.data()->stopDebugger(); } } void DebugController::interruptDebugger() { if (m_currentSession) { m_currentSession.data()->interruptDebugger(); } } void DebugController::run() { if (m_currentSession) { m_currentSession.data()->run(); } else { auto runController = ICore::self()->runController(); if (runController->launchConfigurations().isEmpty()) { runController->showConfigurationDialog(); } runController->executeDefaultLaunch(QStringLiteral("debug")); } } void DebugController::runToCursor() { if (m_currentSession) { m_currentSession.data()->runToCursor(); } } void DebugController::jumpToCursor() { if (m_currentSession) { m_currentSession.data()->jumpToCursor(); } } void DebugController::stepOver() { if (m_currentSession) { m_currentSession.data()->stepOver(); } } void DebugController::stepIntoInstruction() { if (m_currentSession) { m_currentSession.data()->stepIntoInstruction(); } } void DebugController::stepInto() { if (m_currentSession) { m_currentSession.data()->stepInto(); } } void DebugController::stepOverInstruction() { if (m_currentSession) { m_currentSession.data()->stepOverInstruction(); } } void DebugController::stepOut() { if (m_currentSession) { m_currentSession.data()->stepOut(); } } void DebugController::areaChanged(Sublime::Area* newArea) { if (newArea->objectName()!=QLatin1String("debug")) { stopDebugger(); } } void DebugController::toggleBreakpoint() { if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = document->cursorPosition(); if (!cursor.isValid()) return; breakpointModel()->toggleBreakpoint(document->url(), cursor); } } void DebugController::showCurrentLine() { const auto location = qMakePair(m_currentSession->currentUrl(), m_currentSession->currentLine()); if (location.second != -1) { const auto localLocation = m_currentSession->convertToLocalUrl(location); ICore::self()->documentController()->openDocument(localLocation.first, KTextEditor::Cursor(localLocation.second, 0), IDocumentController::DefaultMode); } } const QPixmap* DebugController::executionPointPixmap() { constexpr int markPixmapSize = 32; static QPixmap pixmap=QIcon::fromTheme(QStringLiteral("go-next")).pixmap(QSize(markPixmapSize, markPixmapSize), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/kdevplatform/shell/documentationcontroller.cpp b/kdevplatform/shell/documentationcontroller.cpp index d1c34d448f..a769e509c3 100644 --- a/kdevplatform/shell/documentationcontroller.cpp +++ b/kdevplatform/shell/documentationcontroller.cpp @@ -1,257 +1,257 @@ /* Copyright 2009 Aleix Pol Gonzalez Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentationcontroller.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /** * Return a "more useful" declaration that documentation providers can look-up * * @code * QPoint point; * ^-- cursor here * @endcode * * In this case, this method returns a Declaration pointer to the *type* * instead of a pointer to the instance, which is more useful when looking for help * * @return A more appropriate Declaration pointer or the given parameter @p decl */ Declaration* usefulDeclaration(Declaration* decl) { if (!decl) return nullptr; // First: Attempt to find the declaration of a definition decl = DUChainUtils::declarationForDefinition(decl); // Convenience feature: Retrieve the type declaration of instances, // it makes no sense to pass the declaration pointer of instances of types if (decl->kind() == Declaration::Instance) { AbstractType::Ptr type = TypeUtils::targetTypeKeepAliases(decl->abstractType(), decl->topContext()); auto* idType = dynamic_cast(type.data()); Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : nullptr; if (idDecl) { decl = idDecl; } } return decl; } } class DocumentationViewFactory: public KDevelop::IToolViewFactory { public: DocumentationViewFactory() {} QWidget* create(QWidget *parent = nullptr) override { if (!m_providersModel) { m_providersModel.reset(new ProvidersModel); } return new DocumentationView(parent, m_providersModel.data()); } Qt::DockWidgetArea defaultPosition() const override { return Qt::RightDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.DocumentationView"); } QList contextMenuActions(QWidget* viewWidget) const override { auto documentationViewWidget = qobject_cast(viewWidget); Q_ASSERT(documentationViewWidget); return documentationViewWidget->contextMenuActions(); } private: QScopedPointer m_providersModel; }; DocumentationController::DocumentationController(Core* core) : m_factory(new DocumentationViewFactory) { m_showDocumentation = core->uiController()->activeMainWindow()->actionCollection()->addAction(QStringLiteral("showDocumentation")); - m_showDocumentation->setText(i18n("Show Documentation")); + m_showDocumentation->setText(i18nc("@action", "Show Documentation")); m_showDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("documentation"))); connect(m_showDocumentation, &QAction::triggered, this, &DocumentationController::doShowDocumentation); // registering the tool view here so it registered before the areas are restored // and thus also gets treated like the ones registered from plugins // cmp. comment about tool views in CorePrivate::initialize - core->uiController()->addToolView(i18n("Documentation"), m_factory); + core->uiController()->addToolView(i18nc("@title:window", "Documentation"), m_factory); } DocumentationController::~DocumentationController() { } void DocumentationController::initialize() { } void KDevelop::DocumentationController::doShowDocumentation() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = usefulDeclaration(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); auto documentation = documentationForDeclaration(decl); if (documentation) { showDocumentation(documentation); } } KDevelop::ContextMenuExtension KDevelop::DocumentationController::contextMenuExtension(Context* context, QWidget* parent) { Q_UNUSED(parent); ContextMenuExtension menuExt; auto* ctx = dynamic_cast(context); if(ctx) { DUChainReadLocker lock(DUChain::lock()); if(!ctx->declaration().data()) return menuExt; auto doc = documentationForDeclaration(ctx->declaration().data()); if (doc) { menuExt.addAction(ContextMenuExtension::ExtensionGroup, m_showDocumentation); } } return menuExt; } IDocumentation::Ptr DocumentationController::documentationForDeclaration(Declaration* decl) { if (!decl) return {}; const auto documentationProviders = this->documentationProviders(); for (IDocumentationProvider* doc : documentationProviders) { qCDebug(SHELL) << "Documentation provider found:" << doc; auto ret = doc->documentationForDeclaration(decl); qCDebug(SHELL) << "Documentation proposed: " << ret.data(); if (ret) { return ret; } } return {}; } IDocumentation::Ptr DocumentationController::documentation(const QUrl& url) const { const auto providers = this->documentationProviders(); for (const IDocumentationProvider* provider : providers) { IDocumentation::Ptr doc = provider->documentation(url); if (doc) { return doc; } } return {}; } QList< IDocumentationProvider* > DocumentationController::documentationProviders() const { const QList plugins = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProvider")); const QList pluginsProvider = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProviderProvider")); QList ret; for (IPlugin* p : pluginsProvider) { auto *docProvider=p->extension(); if (!docProvider) { qCWarning(SHELL) << "plugin" << p << "does not implement ProviderProvider extension, rerun kbuildsycoca5"; continue; } ret.append(docProvider->providers()); } for (IPlugin* p : plugins) { auto *doc=p->extension(); if (!doc) { qCWarning(SHELL) << "plugin" << p << "does not implement Provider extension, rerun kbuildsycoca5"; continue; } ret.append(doc); } return ret; } void KDevelop::DocumentationController::showDocumentation(const IDocumentation::Ptr& doc) { Q_ASSERT_X(doc, Q_FUNC_INFO, "Null documentation pointer is unsupported."); - QWidget* w = ICore::self()->uiController()->findToolView(i18n("Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise); + QWidget* w = ICore::self()->uiController()->findToolView(i18nc("@title:window", "Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise); if(!w) { qCWarning(SHELL) << "Could not add documentation tool view"; return; } auto* view = dynamic_cast(w); if( !view ) { qCWarning(SHELL) << "Could not cast tool view" << w << "to DocumentationView class!"; return; } view->showDocumentation(doc); } void DocumentationController::changedDocumentationProviders() { emit providersChanged(); } diff --git a/kdevplatform/shell/documentcontroller.cpp b/kdevplatform/shell/documentcontroller.cpp index 35af0d9594..ed9da1b2a1 100644 --- a/kdevplatform/shell/documentcontroller.cpp +++ b/kdevplatform/shell/documentcontroller.cpp @@ -1,1272 +1,1272 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include "debug.h" #include #include #define EMPTY_DOCUMENT_URL i18n("Untitled") using namespace KDevelop; class KDevelop::DocumentControllerPrivate { public: struct OpenFileResult { QList urls; QString encoding; }; explicit DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(nullptr) { } ~DocumentControllerPrivate() = default; // used to map urls to open docs QHash< QUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const QUrl & u, const KTextEditor::Cursor& cursor ); QUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { const QList urlsForDoc = documents.keys(qobject_cast(doc)); for (const QUrl& url : urlsForDoc) { qCDebug(SHELL) << "destroying document" << doc; documents.remove(url); } } OpenFileResult showOpenFile() const { QUrl dir; if ( controller->activeDocument() ) { dir = controller->activeDocument()->url().adjusted(QUrl::RemoveFilename); } else { const auto cfg = KSharedConfig::openConfig()->group("Open File"); dir = cfg.readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } - const auto caption = i18n("Open File"); + const auto caption = i18nc("@title:window", "Open File"); const auto filter = i18n("*|Text File\n"); auto parent = Core::self()->uiControllerInternal()->defaultMainWindow(); // use special dialogs in a KDE session, native dialogs elsewhere if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { const auto result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), dir, filter, parent, caption); return {result.URLs, result.encoding}; } // note: can't just filter on text files using the native dialog, just display all files // see https://phabricator.kde.org/D622#11679 const auto urls = QFileDialog::getOpenFileUrls(parent, caption, dir); return {urls, QString()}; } void chooseDocument() { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) { QString encoding = res.encoding; for (const QUrl& u : res.urls) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { const auto documentIt = documents.constFind(document->url()); if (documentIt != documents.constEnd()) { // Weird situation (saving as a file that is already open) IDocument* origDoc = *documentIt; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const QUrl &url, IBuddyDocumentFinder* finder) { const QList allDocs = controller->openDocuments(); for (KDevelop::IDocument* doc : allDocs) { if(finder->areBuddies(url, doc->url())) { return doc; } } return nullptr; } static bool fileExists(const QUrl& url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } else { #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) auto job = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatNoDetails, KIO::HideProgressInfo); #else auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo); #endif KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); return job->exec(); } }; IDocument* openDocumentInternal( const QUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = QString(), DocumentController::DocumentActivationParams activationParams = {}, IDocument* buddy = nullptr) { Q_ASSERT(!inputUrl.isRelative()); Q_ASSERT(!inputUrl.fileName().isEmpty() || !inputUrl.isLocalFile()); QString _encoding = encoding; QUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) url = res.urls.first(); _encoding = res.encoding; if ( url.isEmpty() ) //still no url return nullptr; } KSharedConfig::openConfig()->group("Open File").writeEntry( "Last Open File Directory", url.adjusted(QUrl::RemoveFilename) ); // clean it and resolve possible symlink url = url.adjusted( QUrl::NormalizePathSegments ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url = QUrl::fromLocalFile( path ); } //get a part document IDocument* doc = documents.value(url); if (!doc) { QMimeType mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else if (!url.isValid()) { // Exit if the url is invalid (should not happen) // If the url is valid and the file does not already exist, // kate creates the file and gives a message saying so qCDebug(SHELL) << "invalid URL:" << url.url(); return nullptr; } else if (KProtocolInfo::isKnownProtocol(url.scheme()) && !fileExists(url)) { //Don't create a new file if we are not in the code mode. if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("code")) { return nullptr; } // enfore text mime type in order to create a kate part editor which then can be used to create the file // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434 mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else { mimeType = QMimeDatabase().mimeTypeForUrl(url); if(!url.isLocalFile() && mimeType.isDefault()) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } } // is the URL pointing to a directory? if (mimeType.inherits(QStringLiteral("inode/directory"))) { qCDebug(SHELL) << "cannot open directory:" << url.url(); return nullptr; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-SupportedMimeTypes"), mimeType.name()); Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints); } if( IDocumentFactory* factory = factories.value(mimeType.name())) { doc = factory->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(nullptr, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("AskOpenWithTextEditor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return nullptr; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return nullptr; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = nullptr) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::View* previousActiveTextView = ICore::self()->documentController()->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); auto *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, &Sublime::Document::aboutToDelete, controller, &DocumentController::notifyDocumentClosed); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView()); const auto& views = sdoc->views(); auto it = std::find_if(views.begin(), views.end(), [&](Sublime::View* view) { Sublime::AreaIndex* areaIdx = area->indexOf(view); return (areaIdx && areaIdx == activeViewIdx); }); Sublime::View* partView = (it != views.end()) ? *it : nullptr; bool addView = false; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = nullptr; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies() && !buddy && doc->mimeType().isValid()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document QString mime = doc->mimeType().name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { auto* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab const auto& activeAreaViews = pActiveViewIndex->views(); const auto& buddyViews = sublimeDocBuddy->views(); auto it = std::find_if(activeAreaViews.begin(), activeAreaViews.end(), [&](Sublime::View* view) { return buddyViews.contains(view); }); if (it != activeAreaViews.end()) { buddyView = *it; } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = nullptr; IBuddyDocumentFinder *buddyFinder = nullptr; if(activeView) activeDoc = qobject_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = QMimeDatabase().mimeTypeForUrl(activeDoc->url()).name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, nullptr); Sublime::UrlDocument *activeDoc = nullptr, *afterActiveDoc = nullptr; if(activeView && afterActiveView) { activeDoc = qobject_cast(activeView->document()); afterActiveDoc = qobject_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no problem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView( partView, !activationParams.testFlag(IDocumentController::DoNotFocus)); } if (!activationParams.testFlag(IDocumentController::DoNotAddToRecentOpen) && !controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(KTextEditor::View* v = doc->activeTextView()) activePosition = v->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); return true; } DocumentController* const controller; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; }; Q_DECLARE_TYPEINFO(KDevelop::DocumentControllerPrivate::HistoryEntry, Q_MOVABLE_TYPE); DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) , d_ptr(new DocumentControllerPrivate(this)) { setObjectName(QStringLiteral("DocumentController")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/DocumentController"), this, QDBusConnection::ExportScriptableSlots ); connect(this, &DocumentController::documentUrlChanged, this, [this] (IDocument* document) { Q_D(DocumentController); d->changeDocumentUrl(document); }); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void DocumentController::initialize() { } void DocumentController::cleanup() { Q_D(DocumentController); if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. const auto documents = openDocuments(); for (IDocument* doc : documents) { doc->close(IDocument::Discard); } } DocumentController::~DocumentController() = default; void DocumentController::setupActions() { Q_D(DocumentController); KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction( QStringLiteral("file_open") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_O ); - action->setText(i18n( "&Open..." ) ); + action->setText(i18nc("@action", "&Open..." ) ); connect(action, &QAction::triggered, this, [this] { Q_D(DocumentController); d->chooseDocument(); } ); - action->setToolTip( i18n( "Open file" ) ); - action->setWhatsThis( i18n( "Opens a file for editing." ) ); + action->setToolTip( i18nc("@info:tooltip", "Open file" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Opens a file for editing." ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(QUrl)), ac); - d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); + d->fileOpenRecent->setWhatsThis(i18nc("@info:whatsthis", "This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); action = d->saveAll = ac->addAction( QStringLiteral("file_save_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); - action->setText(i18n( "Save Al&l" ) ); + action->setText(i18nc("@action", "Save Al&l" ) ); connect( action, &QAction::triggered, this, &DocumentController::slotSaveAllDocuments ); - action->setToolTip( i18n( "Save all open documents" ) ); - action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) ); + action->setToolTip( i18nc("@info:tooltip", "Save all open documents" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Save all open documents, prompting for additional information when necessary." ) ); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( QStringLiteral("file_revert_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); - action->setText(i18n( "Reload All" ) ); + action->setText(i18nc("@action", "Reload All" ) ); connect( action, &QAction::triggered, this, &DocumentController::reloadAllDocuments ); - action->setToolTip( i18n( "Revert all open documents" ) ); - action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) ); + action->setToolTip( i18nc("@info:tooltip", "Revert all open documents" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Revert all open documents, returning to the previously saved state." ) ); action->setEnabled(false); action = d->close = ac->addAction( QStringLiteral("file_close") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_W ); - action->setText( i18n( "&Close" ) ); + action->setText( i18nc("@action", "&Close" ) ); connect( action, &QAction::triggered, this, &DocumentController::fileClose ); - action->setToolTip( i18n( "Close file" ) ); - action->setWhatsThis( i18n( "Closes current file." ) ); + action->setToolTip( i18nc("@info:tooltip", "Close file" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Closes current file." ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( QStringLiteral("file_close_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); - action->setText(i18n( "Clos&e All" ) ); + action->setText(i18nc("@action", "Clos&e All" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllDocuments ); - action->setToolTip( i18n( "Close all open documents" ) ); - action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) ); + action->setToolTip( i18nc("@info:tooltip", "Close all open documents" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Close all open documents, prompting for additional information when necessary." ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( QStringLiteral("file_closeother") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_W ); - action->setText(i18n( "Close All Ot&hers" ) ); + action->setText(i18nc("@action", "Close All Ot&hers" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllOtherDocuments ); - action->setToolTip( i18n( "Close all other documents" ) ); - action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) ); + action->setToolTip( i18nc("@info:tooltip", "Close all other documents" ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Close all open documents, with the exception of the currently active document." ) ); action->setEnabled(false); action = ac->addAction( QStringLiteral("vcsannotate_current_document") ); connect( action, &QAction::triggered, this, &DocumentController::vcsAnnotateCurrentDocument ); - action->setText( i18n( "Show Annotate on current document") ); - action->setIconText( i18n( "Annotate" ) ); + action->setText( i18nc("@action", "Show Annotate on Current Document") ); + action->setIconText( i18nc("@action", "Annotate" ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("user-properties")) ); } void DocumentController::slotOpenDocument(const QUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url, int line, int column ) { return (bool)openDocument( QUrl::fromUserInput(url), KTextEditor::Cursor( line, column ) ); } IDocument* DocumentController::openDocument( const QUrl& inputUrl, const QString& prefName ) { Q_D(DocumentController); return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const QUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { Q_D(DocumentController); return d->openDocumentInternal(inputUrl, QString(), range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { Q_D(DocumentController); return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } bool DocumentController::closeDocument( const QUrl &url ) { Q_D(DocumentController); const auto documentIt = d->documents.constFind(url); if (documentIt == d->documents.constEnd()) return false; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures (*documentIt)->close(); return true; } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { Q_D(DocumentController); auto* doc = qobject_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const QUrl & dirtyUrl ) const { Q_D(const DocumentController); if (dirtyUrl.isEmpty()) { return nullptr; } Q_ASSERT(!dirtyUrl.isRelative()); Q_ASSERT(!dirtyUrl.fileName().isEmpty() || !dirtyUrl.isLocalFile()); //Fix urls that might not be normalized return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), nullptr ); } QList DocumentController::openDocuments() const { Q_D(const DocumentController); QList opened; for (IDocument* doc : qAsConst(d->documents)) { auto *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range, IDocumentController::DoNotAddToRecentOpen); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { const auto documents = modifiedDocuments(list); for (IDocument* doc : documents) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qCWarning(SHELL) << "!! Could not save document:" << doc->url(); else qCWarning(SHELL) << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { ScopedDialog dialog(checkSave, qApp->activeWindow()); return dialog->exec(); } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; const auto documents = openDocuments(); for (IDocument* doc : documents) { if (auto* sdoc = dynamic_cast(doc)) { const auto views = sdoc->views(); auto hasViewInWindow = std::any_of(views.begin(), views.end(), [&](Sublime::View* view) { return (view->hasWidget() && view->widget()->window() == mw); }); if (hasViewInWindow) { list.append(doc); } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; const auto documents = openDocuments(); for (IDocument* doc : documents) { if (auto* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; const auto views = sdoc->views(); for (Sublime::View* view : views) { const auto windows = Core::self()->uiControllerInternal()->mainWindows(); for (Sublime::MainWindow* window : windows) { if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) { inOtherWindow = true; break; } } if (inOtherWindow) { break; } } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; for (IDocument* doc : list) { if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); } return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(qobject_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { const QList views = visibleDocumentsInWindow(qobject_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; for (IDocument* doc : views) { if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } } bool DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { const QList views = visibleDocumentsInWindow(qobject_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return false; for (IDocument* doc : views) { doc->close(IDocument::Discard); } } return true; } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { qCWarning(SHELL) << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(qobject_cast(mw)); soloViews.removeAll(qobject_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; const auto views = mw->area()->views(); for (Sublime::View* view : views) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; return qobject_cast(mw->activeView()->document()); } KTextEditor::View* DocumentController::activeTextDocumentView() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; auto* view = qobject_cast(mw->activeView()); if(!view) return nullptr; return view->textView(); } QString DocumentController::activeDocumentPath( const QString& target ) const { if(!target.isEmpty()) { const auto projects = Core::self()->projectController()->projects(); for (IProject* project : projects) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->path().pathOrUrl() + QLatin1String("/."); } } } IDocument* doc = activeDocument(); if(!doc || target == QLatin1String("[selection]")) { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && !static_cast(selection)->items().isEmpty()) { QString ret = static_cast(selection)->items().at(0)->path().pathOrUrl(); if(static_cast(selection)->items().at(0)->folder()) ret += QLatin1String("/."); return ret; } return QString(); } return doc->url().toString(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; const auto views = uiController->activeSublimeWindow()->area()->views(); for (Sublime::View* view : views) { documents.insert(view->document()->documentSpecifier()); } return documents.values(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { Q_D(DocumentController); if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << QStringLiteral("Text"); } static const QRegularExpression& emptyDocumentPattern() { static const QRegularExpression pattern(QStringLiteral("^/%1(?:\\s\\((\\d+)\\))?$").arg(EMPTY_DOCUMENT_URL)); return pattern; } bool DocumentController::isEmptyDocumentUrl(const QUrl &url) { return emptyDocumentPattern().match(url.toDisplayString(QUrl::PreferLocalFile)).hasMatch(); } QUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; const auto& pattern = emptyDocumentPattern(); const auto openDocuments = Core::self()->documentControllerInternal()->openDocuments(); for (IDocument* doc : openDocuments) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { const auto match = pattern.match(doc->url().toDisplayString(QUrl::PreferLocalFile)); if (match.hasMatch()) { const int num = match.capturedRef(1).toInt(); nextEmptyDocNumber = qMax(nextEmptyDocNumber, num + 1); } else { nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } } QUrl url; if (nextEmptyDocNumber > 0) url = QUrl::fromLocalFile(QStringLiteral("/%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = QUrl::fromLocalFile(QLatin1Char('/') + EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { Q_D(const DocumentController); return d->factories.value(mime); } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->topViews(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); qCDebug(SHELL) << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); qCDebug(SHELL) << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { qCDebug(SHELL) << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) const QStringList separators {QStringLiteral("/"), QStringLiteral("-")}; QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == QLatin1String("[")) { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == QLatin1String("]")) { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ qCDebug(SHELL) << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { for (const QStringList& group : qAsConst(groups)) { ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); } }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); auto *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << QStringLiteral("[") << QStringLiteral("]"); // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == QLatin1String("/") ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { qCDebug(SHELL) << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); qCDebug(SHELL) << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { qCDebug(SHELL) << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { qCDebug(SHELL) << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::vcsAnnotateCurrentDocument() { IDocument* doc = activeDocument(); if (!doc) return; QUrl url = doc->url(); IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { auto* iface = project->versionControlPlugin()->extension(); auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface); connect(doc->textDocument(), &KTextEditor::Document::aboutToClose, helper, QOverload::of(&VcsPluginHelper::disposeEventually)); Q_ASSERT(qobject_cast(doc->activeTextView())); // can't use new signal slot syntax here, AnnotationViewInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)), helper, SLOT(disposeEventually(View*,bool))); helper->addContextDocument(url); helper->annotation(); } else { const QString messageText = i18n("Could not annotate the document because it is not part of a version-controlled project."); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); } } #include "moc_documentcontroller.cpp" diff --git a/kdevplatform/shell/editorconfigpage.cpp b/kdevplatform/shell/editorconfigpage.cpp index 5cc1ba7986..32521f58e4 100644 --- a/kdevplatform/shell/editorconfigpage.cpp +++ b/kdevplatform/shell/editorconfigpage.cpp @@ -1,128 +1,128 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "editorconfigpage.h" #include #include #include #include using namespace KDevelop; namespace { class KTextEditorConfigPageAdapter : public ConfigPage { Q_OBJECT public: explicit KTextEditorConfigPageAdapter(KTextEditor::ConfigPage* page, QWidget* parent = nullptr) : ConfigPage(nullptr, nullptr, parent), m_page(page) { page->setParent(this); auto* layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(page); setLayout(layout); connect(page, &KTextEditor::ConfigPage::changed, this, &ConfigPage::changed); } ~KTextEditorConfigPageAdapter() override {} QString name() const override { return m_page->name(); } QIcon icon() const override { return m_page->icon(); } QString fullName() const override { return m_page->fullName(); } public Q_SLOTS: void apply() override { m_page->apply(); } void defaults() override { m_page->defaults(); } void reset() override { m_page->reset(); } private: KTextEditor::ConfigPage* const m_page; }; } EditorConfigPage::EditorConfigPage(QWidget* parent) : ConfigPage(nullptr, nullptr, parent) { setObjectName(QStringLiteral("editorconfig")); } EditorConfigPage::~EditorConfigPage() {} QString EditorConfigPage::name() const { - return i18n("Editor"); + return i18nc("@title:tab", "Editor"); } QIcon EditorConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } QString EditorConfigPage::fullName() const { return i18n("Configure Text Editor"); } int EditorConfigPage::childPages() const { return KTextEditor::Editor::instance()->configPages(); } ConfigPage* EditorConfigPage::childPage(int number) { auto page = KTextEditor::Editor::instance()->configPage(number, this); if (page) { return new KTextEditorConfigPageAdapter(page, this); } return nullptr; } #include "editorconfigpage.moc" diff --git a/kdevplatform/shell/environmentconfigurebutton.cpp b/kdevplatform/shell/environmentconfigurebutton.cpp index 525b8e1cde..c1ede7d633 100644 --- a/kdevplatform/shell/environmentconfigurebutton.cpp +++ b/kdevplatform/shell/environmentconfigurebutton.cpp @@ -1,108 +1,108 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentconfigurebutton.h" #include #include "settings/environmentpreferences.h" #include #include #include #include #include #include #include namespace KDevelop { class EnvironmentConfigureButtonPrivate { public: explicit EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) : q(_q), selectionWidget(nullptr) { } void showDialog() { ScopedDialog dlg(qApp->activeWindow()); QString selected; if (selectionWidget) { selected = selectionWidget->effectiveProfileName(); } auto prefs = new EnvironmentPreferences(selected, q); // TODO: This should be implicit when constructing EnvironmentPreferences prefs->initConfigManager(); prefs->reset(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, dlg.data(), &QDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(prefs); layout->addWidget(buttonBox); dlg->setLayout(layout); dlg->setWindowTitle(prefs->fullName()); dlg->setWindowIcon(prefs->icon()); dlg->resize(800, 600); if (dlg->exec() == QDialog::Accepted) { prefs->apply(); emit q->environmentConfigured(); } } EnvironmentConfigureButton* const q; EnvironmentSelectionWidget *selectionWidget; }; EnvironmentConfigureButton::EnvironmentConfigureButton(QWidget* parent) : QPushButton(parent), d_ptr(new EnvironmentConfigureButtonPrivate(this)) { setText(QString()); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setIcon(QIcon::fromTheme(QStringLiteral("configure"))); - setToolTip(i18n("Configure environment variables")); + setToolTip(i18nc("@info:tooltip", "Configure environment variables")); connect(this, &EnvironmentConfigureButton::clicked, this, [this] { Q_D(EnvironmentConfigureButton); d->showDialog(); }); } EnvironmentConfigureButton::~EnvironmentConfigureButton() = default; void EnvironmentConfigureButton::setSelectionWidget(EnvironmentSelectionWidget* widget) { Q_D(EnvironmentConfigureButton); connect(this, &EnvironmentConfigureButton::environmentConfigured, widget, &EnvironmentSelectionWidget::reconfigure); d->selectionWidget = widget; } } #include "moc_environmentconfigurebutton.cpp" diff --git a/kdevplatform/shell/launchconfigurationdialog.cpp b/kdevplatform/shell/launchconfigurationdialog.cpp index a6854934c8..0fe47e12c6 100644 --- a/kdevplatform/shell/launchconfigurationdialog.cpp +++ b/kdevplatform/shell/launchconfigurationdialog.cpp @@ -1,1018 +1,1018 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "launchconfigurationdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "runcontroller.h" #include "launchconfiguration.h" #include "debug.h" #include #include #include namespace KDevelop { bool launchConfigGreaterThan(KDevelop::LaunchConfigurationType* a, KDevelop::LaunchConfigurationType* b) { return a->name()>b->name(); } //TODO: Maybe use KPageDialog instead, might make the model stuff easier and the default-size stuff as well LaunchConfigurationDialog::LaunchConfigurationDialog(QWidget* parent) : QDialog(parent) { - setWindowTitle( i18n( "Launch Configurations" ) ); + setWindowTitle( i18nc("@title:window", "Launch Configurations" ) ); auto* mainWidget = new QWidget(this); auto *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); setupUi(mainWidget); splitter->setSizes(QList{260, 620}); splitter->setCollapsible(0, false); addConfig->setToolTip(i18nc("@info:tooltip", "Add a new launch configuration.")); deleteConfig->setEnabled( false ); deleteConfig->setToolTip(i18nc("@info:tooltip", "Delete selected launch configuration.")); model = new LaunchConfigurationsModel( tree ); tree->setModel( model ); tree->setExpandsOnDoubleClick( true ); tree->setSelectionBehavior( QAbstractItemView::SelectRows ); tree->setSelectionMode( QAbstractItemView::SingleSelection ); tree->setUniformRowHeights( true ); tree->setItemDelegate( new LaunchConfigurationModelDelegate(this) ); tree->setColumnHidden(1, true); for(int row=0; rowrowCount(); row++) { tree->setExpanded(model->index(row, 0), true); } tree->setContextMenuPolicy(Qt::CustomContextMenu); connect( tree, &QTreeView::customContextMenuRequested, this, &LaunchConfigurationDialog::doTreeContextMenu ); connect( deleteConfig, &QPushButton::clicked, this, &LaunchConfigurationDialog::deleteConfiguration); connect( model, &LaunchConfigurationsModel::dataChanged, this, &LaunchConfigurationDialog::modelChanged ); connect( tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &LaunchConfigurationDialog::selectionChanged); QModelIndex idx = model->indexForConfig( Core::self()->runControllerInternal()->defaultLaunch() ); qCDebug(SHELL) << "selecting index:" << idx; if( !idx.isValid() ) { for( int i = 0; i < model->rowCount(); i++ ) { if( model->rowCount( model->index( i, 0, QModelIndex() ) ) > 0 ) { idx = model->index( 1, 0, model->index( i, 0, QModelIndex() ) ); break; } } if( !idx.isValid() ) { idx = model->index( 0, 0, QModelIndex() ); } } tree->selectionModel()->select( QItemSelection( idx, idx ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); // Unfortunately tree->resizeColumnToContents() only looks at the top-level // items, instead of all open ones. Hence we're calculating it ourselves like // this: // Take the selected index, check if it has childs, if so take the first child // Then count the level by going up, then let the tree calculate the width // for the selected or its first child index and add indentation*level // // If Qt Software ever fixes resizeColumnToContents, the following line // can be enabled and the rest be removed // tree->resizeColumnToContents( 0 ); int level = 0; QModelIndex widthidx = idx; if( model->rowCount( idx ) > 0 ) { widthidx = model->index( 0, 0, idx ); } QModelIndex parentidx = widthidx.parent(); while( parentidx.isValid() ) { level++; parentidx = parentidx.parent(); } // make sure the base column width is honored, e.g. when no launch configs exist tree->resizeColumnToContents(0); int width = tree->columnWidth( 0 ); while ( widthidx.isValid() ) { width = qMax( width, level*tree->indentation() + tree->indentation() + tree->sizeHintForIndex( widthidx ).width() ); widthidx = widthidx.parent(); } tree->setColumnWidth( 0, width ); auto* m = new QMenu(this); QList types = Core::self()->runController()->launchConfigurationTypes(); std::sort(types.begin(), types.end(), launchConfigGreaterThan); //we want it in reverse order for (LaunchConfigurationType* type : qAsConst(types)) { connect(type, &LaunchConfigurationType::signalAddLaunchConfiguration, this, &LaunchConfigurationDialog::addConfiguration); QMenu* suggestionsMenu = type->launcherSuggestions(); if(suggestionsMenu) { // take ownership suggestionsMenu->setParent(m, suggestionsMenu->windowFlags()); m->addMenu(suggestionsMenu); } } // Simplify menu structure to get rid of 1-entry levels while (m->actions().count() == 1) { QMenu* subMenu = m->actions().at(0)->menu(); if (subMenu && subMenu->isEnabled() && subMenu->actions().count()<5) { m = subMenu; } else { break; } } if(!m->isEmpty()) { auto* separator = new QAction(m); separator->setSeparator(true); m->insertAction(m->actions().at(0), separator); } for (LaunchConfigurationType* type : qAsConst(types)) { auto* action = new QAction(type->icon(), type->name(), m); action->setProperty("configtype", QVariant::fromValue(type)); connect(action, &QAction::triggered, this, &LaunchConfigurationDialog::createEmptyLauncher); if(!m->actions().isEmpty()) m->insertAction(m->actions().at(0), action); else m->addAction(action); } addConfig->setMenu(m); addConfig->setEnabled( !m->isEmpty() ); messageWidget->setCloseButtonVisible( false ); messageWidget->setMessageType( KMessageWidget::Warning ); messageWidget->setText( i18n("No launch configurations available. (Is any of the Execute plugins loaded?)") ); messageWidget->setVisible( m->isEmpty() ); connect(debugger, QOverload::of(&QComboBox::currentIndexChanged), this, &LaunchConfigurationDialog::launchModeChanged); connect(buttonBox, &QDialogButtonBox::accepted, this, &LaunchConfigurationDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LaunchConfigurationDialog::reject); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig)); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig)); mainLayout->addWidget(buttonBox); resize( QSize(qMax(1200, sizeHint().width()), qMax(500, sizeHint().height())) ); } void LaunchConfigurationDialog::doTreeContextMenu(const QPoint& point) { if ( ! tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex selected = tree->selectionModel()->selectedRows().first(); if ( selected.parent().isValid() && ! selected.parent().parent().isValid() ) { // only display the menu if a launch config is clicked QMenu menu(tree); - auto* rename = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename configuration"), &menu); - auto* delete_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete configuration"), &menu); + auto* rename = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename Configuration"), &menu); + auto* delete_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete Configuration"), &menu); connect(rename, &QAction::triggered, this, &LaunchConfigurationDialog::renameSelected); connect(delete_, &QAction::triggered, this, &LaunchConfigurationDialog::deleteConfiguration); menu.addAction(rename); menu.addAction(delete_); menu.exec(tree->viewport()->mapToGlobal(point)); } } } void LaunchConfigurationDialog::renameSelected() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex parent = tree->selectionModel()->selectedRows().first(); if( parent.parent().isValid() ) { parent = parent.parent(); } QModelIndex index = model->index(tree->selectionModel()->selectedRows().first().row(), 0, parent); tree->edit( index ); } } QSize LaunchConfigurationDialog::sizeHint() const { QSize s = QDialog::sizeHint(); return s.expandedTo(QSize(880, 520)); } void LaunchConfigurationDialog::createEmptyLauncher() { auto* action = qobject_cast(sender()); Q_ASSERT(action); auto* type = qobject_cast(action->property("configtype").value()); Q_ASSERT(type); IProject* p = model->projectForIndex(tree->currentIndex()); QPair< QString, QString > launcher( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); ILaunchConfiguration* l = ICore::self()->runController()->createLaunchConfiguration(type, launcher, p); addConfiguration(l); } void LaunchConfigurationDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if( !deselected.indexes().isEmpty() ) { LaunchConfiguration* l = model->configForIndex( deselected.indexes().first() ); if( l ) { disconnect(l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel); if( currentPageChanged ) { - if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18n("Unsaved Changes") ) == KMessageBox::Yes ) + if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18nc("@title:window", "Unsaved Changes") ) == KMessageBox::Yes ) { saveConfig( deselected.indexes().first() ); } else { auto* tab = qobject_cast( stack->currentWidget() ); tab->setLaunchConfiguration( l ); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } } } updateNameLabel(nullptr); for( int i = 1; i < stack->count(); i++ ) { QWidget* w = stack->widget(i); stack->removeWidget(w); delete w; } if( !selected.indexes().isEmpty() ) { QModelIndex idx = selected.indexes().first(); LaunchConfiguration* l = model->configForIndex( idx ); ILaunchMode* lm = model->modeForIndex( idx ); if( l ) { updateNameLabel( l ); tree->expand( model->indexForConfig( l ) ); connect( l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel ); if( lm ) { QVariant currentLaunchMode = idx.sibling(idx.row(), 1).data(Qt::EditRole); { QSignalBlocker blocker(debugger); const QList launchers = l->type()->launchers(); debugger->clear(); for (ILauncher* launcher : launchers) { if (launcher->supportedModes().contains(lm->id())) { debugger->addItem(launcher->name(), launcher->id()); } } debugger->setCurrentIndex(debugger->findData(currentLaunchMode)); } debugger->setVisible(debugger->count()>0); debugLabel->setVisible(debugger->count()>0); ILauncher* launcher = l->type()->launcherForId( currentLaunchMode.toString() ); if( launcher ) { LaunchConfigPagesContainer* tab = launcherWidgets.value( launcher ); if(!tab) { QList pages = launcher->configPages(); if(!pages.isEmpty()) { tab = new LaunchConfigPagesContainer( launcher->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } } if(tab) { tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); } else { auto* label = new QLabel(i18nc("%1 is a launcher name", "No configuration is needed for '%1'", launcher->name()), stack); label->setAlignment(Qt::AlignCenter); QFont font = label->font(); font.setItalic(true); label->setFont(font); stack->addWidget(label); stack->setCurrentWidget(label); } updateNameLabel( l ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); } else { addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } else { //TODO: enable removal button LaunchConfigurationType* type = l->type(); LaunchConfigPagesContainer* tab = typeWidgets.value( type ); if( !tab ) { tab = new LaunchConfigPagesContainer( type->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } qCDebug(SHELL) << "created pages, setting config up"; tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( true ); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); auto* l = new QLabel(i18n("Select a configuration to edit from the left,
" "or click the \"Add\" button to add a new one.
"), stack); l->setAlignment(Qt::AlignCenter); stack->addWidget(l); stack->setCurrentWidget(l); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { debugger->setVisible( false ); debugLabel->setVisible( false ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } void LaunchConfigurationDialog::saveConfig( const QModelIndex& idx ) { Q_UNUSED( idx ); auto* tab = qobject_cast( stack->currentWidget() ); if( tab ) { tab->save(); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } void LaunchConfigurationDialog::saveConfig() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { saveConfig( tree->selectionModel()->selectedRows().first() ); } } void LaunchConfigurationDialog::pageChanged() { currentPageChanged = true; buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true ); } void LaunchConfigurationDialog::modelChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (tree->selectionModel()) { QModelIndex index = tree->selectionModel()->selectedRows().first(); if (index.row() >= topLeft.row() && index.row() <= bottomRight.row() && bottomRight.column() == 1) selectionChanged(tree->selectionModel()->selection(), tree->selectionModel()->selection()); } } void LaunchConfigurationDialog::deleteConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { model->deleteConfiguration( tree->selectionModel()->selectedRows().first() ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::updateNameLabel( LaunchConfiguration* l ) { if( l ) { configName->setText( i18n("Editing %2: %1", l->name(), l->type()->name() ) ); } else { configName->clear(); } } void LaunchConfigurationDialog::createConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex idx = tree->selectionModel()->selectedRows().first(); if( idx.parent().isValid() ) { idx = idx.parent(); } model->createConfiguration( idx ); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::addConfiguration(ILaunchConfiguration* _launch) { auto* launch = dynamic_cast(_launch); Q_ASSERT(launch); int row = launch->project() ? model->findItemForProject(launch->project())->row : 0; QModelIndex idx = model->index(row, 0); model->addConfiguration(launch, idx); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } LaunchConfigurationsModel::LaunchConfigurationsModel(QObject* parent): QAbstractItemModel(parent) { auto* global = new GenericPageItem; global->text = i18n("Global"); global->row = 0; const auto projects = Core::self()->projectController()->projects(); topItems.reserve(1 + projects.size()); topItems << global; for (IProject* p : projects) { auto* t = new ProjectItem; t->project = p; t->row = topItems.count(); topItems << t; } const auto launchConfigurations = Core::self()->runControllerInternal()->launchConfigurationsInternal(); for (LaunchConfiguration* l : launchConfigurations) { addItemForLaunchConfig( l ); } } void LaunchConfigurationsModel::addItemForLaunchConfig( LaunchConfiguration* l ) { auto* t = new LaunchItem; t->launch = l; TreeItem* parent; if( l->project() ) { parent = findItemForProject( l->project() ); } else { parent = topItems.at(0); } t->parent = parent; t->row = parent->children.count(); parent->children.append( t ); addLaunchModeItemsForLaunchConfig ( t ); } void LaunchConfigurationsModel::addLaunchModeItemsForLaunchConfig ( LaunchItem* t ) { QList items; QSet modes; const auto launchers = t->launch->type()->launchers(); for (ILauncher* launcher : launchers) { const auto supportedModes = launcher->supportedModes(); for (const QString& mode : supportedModes) { if( !modes.contains( mode ) && launcher->configPages().count() > 0 ) { modes.insert( mode ); auto* lmi = new LaunchModeItem; lmi->mode = Core::self()->runController()->launchModeForId( mode ); lmi->parent = t; lmi->row = t->children.count(); items.append( lmi ); } } } if( !items.isEmpty() ) { QModelIndex p = indexForConfig( t->launch ); beginInsertRows( p, t->children.count(), t->children.count() + items.count() - 1 ); t->children.append( items ); endInsertRows(); } } LaunchConfigurationsModel::ProjectItem* LaunchConfigurationsModel::findItemForProject(IProject* p) const { for (TreeItem* t : topItems) { auto* pi = dynamic_cast( t ); if( pi && pi->project == p ) { return pi; } } Q_ASSERT(false); return nullptr; } int LaunchConfigurationsModel::columnCount(const QModelIndex& parent) const { Q_UNUSED( parent ); return 2; } QVariant LaunchConfigurationsModel::data(const QModelIndex& index, int role) const { if( index.isValid() && index.column() >= 0 && index.column() < 2 ) { auto* t = static_cast( index.internalPointer() ); switch( role ) { case Qt::DisplayRole: { auto* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if( index.column() == 1 ) { return li->launch->type()->name(); } } auto* pi = dynamic_cast( t ); if( pi && index.column() == 0 ) { return pi->project->name(); } auto* gpi = dynamic_cast( t ); if( gpi && index.column() == 0 ) { return gpi->text; } auto* lmi = dynamic_cast( t ); if( lmi ) { if( index.column() == 0 ) { return lmi->mode->name(); } else if( index.column() == 1 ) { LaunchConfiguration* l = configForIndex( index ); return l->type()->launcherForId( l->launcherForMode( lmi->mode->id() ) )->name(); } } break; } case Qt::DecorationRole: { auto* li = dynamic_cast( t ); if( index.column() == 0 && li ) { return li->launch->type()->icon(); } auto* lmi = dynamic_cast( t ); if( lmi && index.column() == 0 ) { return lmi->mode->icon(); } if ( index.column() == 0 && !index.parent().isValid() ) { if (index.row() == 0) { // global item return QIcon::fromTheme(QStringLiteral("folder")); } else { // project item return QIcon::fromTheme(QStringLiteral("folder-development")); } } break; } case Qt::EditRole: { auto* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if ( index.column() == 1 ) { return li->launch->type()->id(); } } auto* lmi = dynamic_cast( t ); if( lmi && index.column() == 1 ) { return configForIndex( index )->launcherForMode( lmi->mode->id() ); } break; } default: break; } } return QVariant(); } QModelIndex LaunchConfigurationsModel::index(int row, int column, const QModelIndex& parent) const { if( !hasIndex( row, column, parent ) ) return QModelIndex(); TreeItem* tree; if( !parent.isValid() ) { tree = topItems.at( row ); } else { auto* t = static_cast( parent.internalPointer() ); tree = t->children.at( row ); } if( tree ) { return createIndex( row, column, tree ); } return QModelIndex(); } QModelIndex LaunchConfigurationsModel::parent(const QModelIndex& child) const { if( child.isValid() ) { auto* t = static_cast( child.internalPointer() ); if( t->parent ) { return createIndex( t->parent->row, 0, t->parent ); } } return QModelIndex(); } int LaunchConfigurationsModel::rowCount(const QModelIndex& parent) const { if( parent.column() > 0 ) return 0; if( parent.isValid() ) { auto* t = static_cast( parent.internalPointer() ); return t->children.count(); } else { return topItems.count(); } return 0; } QVariant LaunchConfigurationsModel::headerData(int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { if( section == 0 ) { - return i18nc("Name of the Launch Configurations", "Name"); + return i18nc("@title:column Name of the Launch Configurations", "Name"); } else if( section == 1 ) { - return i18nc("The type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type"); + return i18nc("@title:column Type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type"); } } return QVariant(); } Qt::ItemFlags LaunchConfigurationsModel::flags(const QModelIndex& index) const { if( index.isValid() && index.column() >= 0 && index.column() < columnCount( QModelIndex() ) ) { auto* t = static_cast( index.internalPointer() ); if( t && ( dynamic_cast( t ) || ( dynamic_cast( t ) && index.column() == 1 ) ) ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable ); } else if( t ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); } } return Qt::NoItemFlags; } bool LaunchConfigurationsModel::setData(const QModelIndex& index, const QVariant& value, int role) { if( index.isValid() && index.parent().isValid() && role == Qt::EditRole ) { if( index.row() >= 0 && index.row() < rowCount( index.parent() ) ) { auto* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( t ) { if( index.column() == 0 ) { t->launch->setName( value.toString() ); } else if( index.column() == 1 ) { if (t->launch->type()->id() != value.toString()) { t->launch->setType( value.toString() ); QModelIndex p = indexForConfig(t->launch); qCDebug(SHELL) << data(p); beginRemoveRows( p, 0, t->children.count() ); qDeleteAll( t->children ); t->children.clear(); endRemoveRows(); addLaunchModeItemsForLaunchConfig( t ); } } emit dataChanged(index, index); return true; } auto* lmi = dynamic_cast( static_cast( index.internalPointer() ) ); if( lmi ) { if( index.column() == 1 && index.data(Qt::EditRole)!=value) { LaunchConfiguration* l = configForIndex( index ); l->setLauncherForMode( lmi->mode->id(), value.toString() ); emit dataChanged(index, index); return true; } } } } return false; } ILaunchMode* LaunchConfigurationsModel::modeForIndex( const QModelIndex& idx ) const { if( idx.isValid() ) { auto* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->mode; } } return nullptr; } LaunchConfiguration* LaunchConfigurationsModel::configForIndex(const QModelIndex& idx ) const { if( idx.isValid() ) { auto* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->launch; } auto* lmitem = dynamic_cast( static_cast( idx.internalPointer() ) ); if( lmitem ) { return dynamic_cast( lmitem->parent )->launch; } } return nullptr; } QModelIndex LaunchConfigurationsModel::indexForConfig( LaunchConfiguration* l ) const { if( l ) { TreeItem* tparent = topItems.at( 0 ); if( l->project() ) { for (TreeItem* t : topItems) { auto* pi = dynamic_cast( t ); if( pi && pi->project == l->project() ) { tparent = t; break; } } } if( tparent ) { for (TreeItem* c : qAsConst(tparent->children)) { auto* li = dynamic_cast( c ); if( li->launch && li->launch == l ) { return index( c->row, 0, index( tparent->row, 0, QModelIndex() ) ); } } } } return QModelIndex(); } void LaunchConfigurationsModel::deleteConfiguration( const QModelIndex& index ) { auto* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( !t ) return; beginRemoveRows( parent( index ), index.row(), index.row() ); t->parent->children.removeAll( t ); Core::self()->runControllerInternal()->removeLaunchConfiguration( t->launch ); endRemoveRows(); } void LaunchConfigurationsModel::createConfiguration(const QModelIndex& parent ) { if(!Core::self()->runController()->launchConfigurationTypes().isEmpty()) { auto* t = static_cast( parent.internalPointer() ); auto* ti = dynamic_cast( t ); LaunchConfigurationType* type = Core::self()->runController()->launchConfigurationTypes().at(0); QPair launcher = qMakePair( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); IProject* p = ( ti ? ti->project : nullptr ); ILaunchConfiguration* l = Core::self()->runController()->createLaunchConfiguration( type, launcher, p ); addConfiguration(l, parent); } } void LaunchConfigurationsModel::addConfiguration(ILaunchConfiguration* l, const QModelIndex& parent) { if( parent.isValid() ) { beginInsertRows( parent, rowCount( parent ), rowCount( parent ) ); addItemForLaunchConfig( dynamic_cast( l ) ); endInsertRows(); } else { delete l; Q_ASSERT(false && "could not add the configuration"); } } IProject* LaunchConfigurationsModel::projectForIndex(const QModelIndex& idx) { if(idx.parent().isValid()) { return projectForIndex(idx.parent()); } else { const auto* item = dynamic_cast(topItems[idx.row()]); return item ? item->project : nullptr; } } LaunchConfigPagesContainer::LaunchConfigPagesContainer( const QList& factories, QWidget* parent ) : QWidget(parent) { setLayout( new QVBoxLayout( this ) ); layout()->setContentsMargins( 0, 0, 0, 0 ); QWidget* parentwidget = this; QTabWidget* tab = nullptr; if( factories.count() > 1 ) { tab = new QTabWidget( this ); parentwidget = tab; layout()->addWidget( tab ); } for (LaunchConfigurationPageFactory* fac : factories) { LaunchConfigurationPage* page = fac->createWidget( parentwidget ); if ( page->layout() ) { // remove margins for single page, reset margins for tabbed display const int pageMargin = tab ? -1 : 0; page->layout()->setContentsMargins(pageMargin, pageMargin, pageMargin, pageMargin); } pages.append( page ); connect( page, &LaunchConfigurationPage::changed, this, &LaunchConfigPagesContainer::changed ); if( tab ) { tab->addTab( page, page->icon(), page->title() ); } else { layout()->addWidget( page ); } } } void LaunchConfigPagesContainer::setLaunchConfiguration( KDevelop::LaunchConfiguration* l ) { config = l; for (LaunchConfigurationPage* p : qAsConst(pages)) { p->loadFromConfiguration( config->config(), config->project() ); } } void LaunchConfigPagesContainer::save() { for (LaunchConfigurationPage* p : qAsConst(pages)) { p->saveToConfiguration( config->config() ); } config->config().sync(); } QWidget* LaunchConfigurationModelDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const { const auto* model = static_cast(index.model()); ILaunchMode* mode = model->modeForIndex( index ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && mode && config ) { auto* box = new KComboBox( parent ); const QList launchers = config->type()->launchers(); for (auto* launcher : launchers) { if (launcher->supportedModes().contains(mode->id())) { box->addItem(launcher->name(), launcher->id()); } } return box; } else if( !mode && config && index.column() == 1 ) { auto* box = new KComboBox( parent ); const QList types = Core::self()->runController()->launchConfigurationTypes(); for (auto* type : types) { box->addItem(type->name(), type->id()); } return box; } return QStyledItemDelegate::createEditor ( parent, option, index ); } void LaunchConfigurationModelDelegate::setEditorData ( QWidget* editor, const QModelIndex& index ) const { const auto* model = static_cast(index.model()); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && config ) { auto* box = qobject_cast( editor ); box->setCurrentIndex( box->findData( index.data( Qt::EditRole ) ) ); } else { QStyledItemDelegate::setEditorData ( editor, index ); } } void LaunchConfigurationModelDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const { auto* lmodel = static_cast(model); LaunchConfiguration* config = lmodel->configForIndex( index ); if( index.column() == 1 && config ) { auto* box = qobject_cast( editor ); lmodel->setData( index, box->itemData( box->currentIndex() ) ); } else { QStyledItemDelegate::setModelData ( editor, model, index ); } } void LaunchConfigurationDialog::launchModeChanged(int item) { QModelIndex index = tree->currentIndex(); if(debugger->isVisible() && item>=0) tree->model()->setData(index.sibling(index.row(), 1), debugger->itemData(item), Qt::EditRole); } } diff --git a/kdevplatform/shell/launchconfigurationdialog.ui b/kdevplatform/shell/launchconfigurationdialog.ui index 5bfd115b55..23f26aa35b 100644 --- a/kdevplatform/shell/launchconfigurationdialog.ui +++ b/kdevplatform/shell/launchconfigurationdialog.ui @@ -1,154 +1,154 @@ LaunchConfigurationDialog 0 0 643 530 0 Qt::Horizontal - Add + Add - Remove + Remove Qt::Horizontal 1 0 false false 0 0 - Debugger: + Debugger: 0 0 0 0 1 Name 2 0 QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok KMessageWidget QFrame
KMessageWidget
1
diff --git a/kdevplatform/shell/loadedpluginsdialog.cpp b/kdevplatform/shell/loadedpluginsdialog.cpp index f8a9d09b83..f0d5684024 100644 --- a/kdevplatform/shell/loadedpluginsdialog.cpp +++ b/kdevplatform/shell/loadedpluginsdialog.cpp @@ -1,333 +1,333 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "loadedpluginsdialog.h" #include #include #include #include #include #include #include #include #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5,65,0) #include #else #include #endif #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); } QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { Q_OBJECT public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; explicit PluginsModel(QObject* parent = nullptr) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { if (!index.isValid()) return nullptr; if (index.parent().isValid()) return nullptr; if (index.column() != 0) return nullptr; if (index.row() >= m_plugins.count()) return nullptr; return m_plugins[index.row()]; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: { const QString iconName = pluginInfo(plugin).iconName(); if (iconName.isEmpty()) { return QStringLiteral("kdevelop"); } return iconName; } default: return QVariant(); }; } int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: explicit LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = nullptr) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); // only for getting size matters } ~LoadedPluginsDelegate() override { delete pushButton; } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(index); int i = 5; int j = 1; const int iconSize = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize( iconSize + MARGIN * i + pushButton->sizeHint().width() * j, qMax(iconSize + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2) ); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); int iconSize = option.rect.height() - MARGIN * 2; QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); icon.paint(painter, QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets(const QModelIndex &index) const override { Q_UNUSED(index); auto *button = new QPushButton(); button->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); setBlockedEventTypes(button, QList{QEvent::MouseButtonPress, QEvent::MouseButtonRelease, QEvent::MouseButtonDblClick}); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if (widgets.isEmpty()) { return; } auto *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { auto *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { #if KCMUTILS_VERSION >= QT_VERSION_CHECK(5,65,0) const KPluginMetaData pluginInfo = ::pluginInfo(p); if (!pluginInfo.name().isEmpty()) { // Be sure the about data is not completely empty KDevelop::ScopedDialog aboutPlugin(pluginInfo, itemView()); aboutPlugin->exec(); return; } #else KAboutData aboutData = KAboutData::fromPluginMetaData(pluginInfo(p)); if (!aboutData.componentName().isEmpty()) { // Be sure the about data is not completely empty KDevelop::ScopedDialog aboutPlugin(aboutData, itemView()); aboutPlugin->exec(); return; } #endif } } private: QPushButton *pushButton; }; class PluginsView : public QListView { Q_OBJECT public: explicit PluginsView(QWidget* parent = nullptr) :QListView(parent) { setModel(new PluginsModel(this)); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } ~PluginsView() override { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { - setWindowTitle(i18n("Loaded Plugins")); + setWindowTitle(i18nc("@title:window", "Loaded Plugins")); auto* vbox = new QVBoxLayout(this); auto* title = new KTitleWidget(this); #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,63,0) title->setIcon(qApp->windowIcon(), KTitleWidget::ImageLeft); #else title->setPixmap(qApp->windowIcon(), KTitleWidget::ImageLeft); #endif title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); resize(800, 600); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/kdevplatform/shell/mainwindow_p.cpp b/kdevplatform/shell/mainwindow_p.cpp index 499f967271..eab30ff78d 100644 --- a/kdevplatform/shell/mainwindow_p.cpp +++ b/kdevplatform/shell/mainwindow_p.cpp @@ -1,477 +1,477 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "partdocument.h" #include "partcontroller.h" #include "uicontroller.h" #include "statusbar.h" #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" #include "sourceformattercontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include "colorschemechooser.h" #include #include #include #include #include namespace KDevelop { MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_statusBar(nullptr) , lastXMLGUIClientView(nullptr) , m_changingActiveView(false) , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow)) { } void MainWindowPrivate::setupGui() { m_statusBar = new KDevelop::StatusBar(m_mainWindow); setupStatusBar(); } void MainWindowPrivate::setupStatusBar() { QWidget *location = m_mainWindow->statusBarLocation(); if (m_statusBar) location->layout()->addWidget(m_statusBar); } void MainWindowPrivate::addPlugin( IPlugin *plugin ) { qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName(); Q_ASSERT( plugin ); //The direct plugin client can only be added to the first mainwindow if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0]) m_mainWindow->guiFactory()->addClient( plugin ); Q_ASSERT(!m_pluginCustomClients.contains(plugin)); KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow); if(ownClient) { m_pluginCustomClients[plugin] = ownClient; connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->addClient(ownClient); } } void MainWindowPrivate::pluginDestroyed(QObject* pluginObj) { auto* plugin = static_cast(pluginObj); KXMLGUIClient* p = m_pluginCustomClients.take(plugin); m_mainWindow->guiFactory()->removeClient( p ); delete p; } MainWindowPrivate::~MainWindowPrivate() { qDeleteAll(m_pluginCustomClients); } void MainWindowPrivate::removePlugin( IPlugin *plugin ) { Q_ASSERT( plugin ); pluginDestroyed(plugin); disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->removeClient( plugin ); } void MainWindowPrivate::updateSourceFormatterGuiClient(bool hasFormatters) { auto sourceFormatterController = Core::self()->sourceFormatterControllerInternal(); auto guiFactory = m_mainWindow->guiFactory(); if (hasFormatters) { guiFactory->addClient(sourceFormatterController); } else { guiFactory->removeClient(sourceFormatterController); } } void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) m_mainWindow->createGUI(part); } void MainWindowPrivate::changeActiveView(Sublime::View *view) { //disable updates on a window to avoid toolbar flickering on xmlgui client change Sublime::HoldUpdates s(m_mainWindow); mergeView(view); if(!view) return; auto* doc = qobject_cast(view->document()); if (doc) { doc->activate(view, m_mainWindow); } else { //activated view is not a part document so we need to remove active part gui ///@todo adymo: only this window needs to remove GUI // KParts::Part *activePart = Core::self()->partController()->activePart(); // if (activePart) // guiFactory()->removeClient(activePart); } } void MainWindowPrivate::mergeView(Sublime::View* view) { PushPositiveValue block(m_changingActiveView, true); // If the previous view was KXMLGUIClient, remove its actions // In the case that that view was removed, lastActiveView // will auto-reset, and xmlguifactory will disconnect that // client, I think. if (lastXMLGUIClientView) { qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView; m_mainWindow->guiFactory()->removeClient(dynamic_cast(lastXMLGUIClientView)); disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, nullptr); lastXMLGUIClientView = nullptr; } if (!view) return; QWidget* viewWidget = view->widget(); Q_ASSERT(viewWidget); qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow; // If the new view is KXMLGUIClient, add it. if (auto* c = dynamic_cast(viewWidget)) { qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget; lastXMLGUIClientView = viewWidget; m_mainWindow->guiFactory()->addClient(c); connect(viewWidget, &QWidget::destroyed, this, &MainWindowPrivate::xmlguiclientDestroyed); } } void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj) { /* We're informed the QWidget for the active view that is also KXMLGUIclient is dying. KXMLGUIFactory will not like deleted clients, really. Unfortunately, there's nothing we can do at this point. For example, KateView derives from QWidget and KXMLGUIClient. The destroyed() signal is emitted by ~QWidget. At this point, event attempt to cross-cast to KXMLGUIClient is undefined behaviour. We hope to catch view deletion a bit later, but if we fail, we better report it now, rather than get a weird crash a bit later. */ Q_ASSERT(obj == lastXMLGUIClientView); Q_ASSERT(false && "xmlgui clients management is messed up"); Q_UNUSED(obj); } void MainWindowPrivate::setupActions() { connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll); QAction* action; const QString app = qApp->applicationName(); action = KStandardAction::preferences( this, SLOT(settingsDialog()), actionCollection()); - action->setToolTip( i18nc( "%1 = application name", "Configure %1", app ) ); - action->setWhatsThis( i18n( "Lets you customize %1.", app ) ); + action->setToolTip( i18nc( "@info:tooltip %1 = application name", "Configure %1", app ) ); + action->setWhatsThis( i18nc("@info:whatsthis", "Lets you customize %1.", app ) ); action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); - action->setText( i18n("Configure Notifications...") ); + action->setText( i18nc("@action", "Configure Notifications...") ); action->setToolTip( i18nc("@info:tooltip", "Configure notifications") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) ); action = actionCollection()->addAction( QStringLiteral("loaded_plugins"), this, SLOT(showLoadedPlugins()) ); action->setIcon(QIcon::fromTheme(QStringLiteral("plugins"))); - action->setText( i18n("Loaded Plugins") ); - action->setStatusTip( i18n("Show a list of all loaded plugins") ); + action->setText( i18nc("@action", "Loaded Plugins") ); + action->setStatusTip( i18nc("@info:tooltip", "Show a list of all loaded plugins") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_window") ); - action->setText( i18n( "&Next Window" ) ); + action->setText( i18nc("@action", "&Next Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setToolTip( i18nc( "@info:tooltip", "Next window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_window") ); - action->setText( i18n( "&Previous Window" ) ); + action->setText( i18nc("@action", "&Previous Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action = actionCollection()->addAction(QStringLiteral("next_error")); - action->setText(i18n("Jump to Next Outputmark")); + action->setText(i18nc("@action", "Jump to Next Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem); action = actionCollection()->addAction(QStringLiteral("prev_error")); - action->setText(i18n("Jump to Previous Outputmark")); + action->setText(i18nc("@action", "Jump to Previous Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem); action = actionCollection()->addAction( QStringLiteral("split_horizontal") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-top-bottom") )); - action->setText( i18n( "Split View &Top/Bottom" ) ); + action->setText( i18nc("@action", "Split View &Top/Bottom" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_T ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal ); action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) ); action = actionCollection()->addAction( QStringLiteral("split_vertical") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-left-right") )); - action->setText( i18n( "Split View &Left/Right" ) ); + action->setText( i18nc("@action", "Split View &Left/Right" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_L ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical ); action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_split") ); - action->setText( i18n( "&Next Split View" ) ); + action->setText( i18nc("@action", "&Next Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_N ); action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_split") ); - action->setText( i18n( "&Previous Split View" ) ); + action->setText( i18nc("@action", "&Previous Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_P ); action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() ); action = actionCollection()->addAction( QStringLiteral("file_new") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_N ); - action->setText( i18n( "&New" ) ); - action->setIconText( i18nc( "Shorter Text for 'New File' shown in the toolbar", "New") ); + action->setText( i18nc("@action new file", "&New" ) ); + action->setIconText( i18nc( "@action Shorter Text for 'New File' shown in the toolbar", "New") ); connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew ); action->setToolTip( i18nc( "@info:tooltip", "New file" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) ); action = actionCollection()->addAction( QStringLiteral("add_toolview") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_V ); - action->setText( i18n( "&Add Tool View..." ) ); + action->setText( i18nc("@action", "&Add Tool View..." ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView ); action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) ); //Load themes actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new ColorSchemeChooser(actionCollection())); } void MainWindowPrivate::toggleArea(bool b) { if (!b) return; auto* action = qobject_cast(sender()); if (!action) return; m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow); } KActionCollection * MainWindowPrivate::actionCollection() { return m_mainWindow->actionCollection(); } void MainWindowPrivate::registerStatus(QObject* status) { m_statusBar->registerStatus(status); } void MainWindowPrivate::showErrorMessage(const QString& message, int timeout) { m_statusBar->showErrorMessage(message, timeout); } void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { m_tabView = view; QAction* action; - action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split View Top/Bottom")); + action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal); - action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18n("Split View Left/Right")); + action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical); menu->addSeparator(); - action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New File")); + action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@action:inmenu", "New File")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew); if (view) { if (auto* doc = qobject_cast(view->document())) { - action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload")); + action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload")); connect(action, &QAction::triggered, doc, &TextDocument::reload); - action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload All")); + action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload All")); connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll); } } } void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { if (m_tabTooltip.second) { if (m_tabTooltip.first == view) { // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab return; } else { m_tabTooltip.second.data()->close(); } } auto* urlDoc = qobject_cast(view->document()); if (urlDoc) { DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url()); if (top) { if (auto* navigationWidget = top->createNavigationWidget()) { auto* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(container->tabRect(tab)); m_tabTooltip.first = view; m_tabTooltip.second = tooltip; ActiveToolTip::showToolTip(m_tabTooltip.second.data()); } } } } void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { QMenu menu(m_mainWindow); - menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Add Tool View")); + menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@action:inmenu", "Add Tool View")); QHash factories = Core::self()->uiControllerInternal()->factoryDocuments(); QHash actionToFactory; if ( !factories.isEmpty() ) { // sorted actions QMap actionMap; for (QHash::const_iterator it = factories.constBegin(); it != factories.constEnd(); ++it) { auto* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu); action->setIcon(it.value()->statusIcon()); if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) { action->setDisabled(true); } actionToFactory.insert(action, it.key()); actionMap[action->text()] = action; } menu.addActions(actionMap.values()); } auto* lockAction = new QAction(this); lockAction->setCheckable(true); - lockAction->setText(i18n("Lock the Panel from Hiding")); + lockAction->setText(i18nc("@action:inmenu", "Lock the Panel from Hiding")); KConfigGroup config = KSharedConfig::openConfig()->group("UI"); lockAction->setChecked(config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), false)); menu.addSeparator(); menu.addAction(lockAction); QAction* triggered = menu.exec(position); if ( !triggered ) { return; } if (triggered == lockAction) { KConfigGroup config = KSharedConfig::openConfig()->group("UI"); config.writeEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), lockAction->isChecked()); return; } Core::self()->uiControllerInternal()->addToolViewToDockArea( actionToFactory[triggered], area ); } bool MainWindowPrivate::changingActiveView() const { return m_changingActiveView; } KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const { return m_kateWrapper; } } #include "mainwindow_actions.cpp" diff --git a/kdevplatform/shell/openprojectdialog.cpp b/kdevplatform/shell/openprojectdialog.cpp index e7d14e1937..9ef4ade280 100644 --- a/kdevplatform/shell/openprojectdialog.cpp +++ b/kdevplatform/shell/openprojectdialog.cpp @@ -1,366 +1,366 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" #include #include namespace { struct URLInfo { bool isValid; bool isDir; QString extension; }; URLInfo urlInfo(const QUrl& url) { URLInfo ret; ret.isValid = false; if (url.isLocalFile()) { QFileInfo info(url.toLocalFile()); ret.isValid = info.exists(); if (ret.isValid) { ret.isDir = info.isDir(); ret.extension = info.suffix(); } } else if (url.isValid()) { KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname if (ret.isValid) { KIO::UDSEntry entry = statJob->statResult(); ret.isDir = entry.isDir(); ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); } } return ret; } } namespace KDevelop { OpenProjectDialog::OpenProjectDialog(bool fetch, const QUrl& startUrl, const QUrl& repoUrl, IPlugin* vcsOrProviderPlugin, QWidget* parent) : KAssistantDialog( parent ) , m_urlIsDirectory(false) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); // KAssistantDialog creates a help button by default, no option to prevent that auto helpButton = button(QDialogButtonBox::Help); if (helpButton) { buttonBox()->removeButton(helpButton); delete helpButton; } const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); QStringList filters, allEntry; QString filterFormat = useKdeFileDialog ? QStringLiteral("%1|%2 (%1)") : QStringLiteral("%2 (%1)"); allEntry << QLatin1String("*.") + ShellExtension::getInstance()->projectFileExtension(); filters << filterFormat.arg(QLatin1String("*.") + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); const QVector plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); for (const KPluginMetaData& info : plugins) { m_projectPlugins.insert(info.name(), info); QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter")); // some project file manager plugins like KDevGenericManager have no file filter set if (filter.isEmpty()) { m_genericProjectPlugins << info.name(); continue; } QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); m_projectFilters.insert(info.name(), filter); allEntry += filter; filters << filterFormat.arg(filter.join(QLatin1Char(' ')), desc); } if (useKdeFileDialog) filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QLatin1Char(' ')))); QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); KPageWidgetItem* currentPage = nullptr; if( fetch ) { sourcePageWidget = new ProjectSourcePage(start, repoUrl, vcsOrProviderPlugin, this); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); - sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); + sourcePage = addPage( sourcePageWidget, i18nc("@title:tab", "Select Source") ); currentPage = sourcePage; } if (useKdeFileDialog) { openPageWidget = new OpenProjectPage( start, filters, this ); connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " "or any folder to open as a project") ); if (!currentPage) { currentPage = openPage; } } else { - nativeDialog = new QFileDialog(this, i18n("Open Project")); + nativeDialog = new QFileDialog(this, i18nc("@title:window", "Open Project")); nativeDialog->setDirectoryUrl(start); nativeDialog->setFileMode(QFileDialog::Directory); } auto* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); - projectInfoPage = addPage( page, i18n("Project Information") ); + projectInfoPage = addPage( page, i18nc("@title:tab", "Project Information") ); if (!currentPage) { currentPage = projectInfoPage; } setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); - setWindowTitle(i18n("Open Project")); + setWindowTitle(i18nc("@title:window", "Open Project")); } bool OpenProjectDialog::execNativeDialog() { while (true) { if (nativeDialog->exec()) { QUrl selectedUrl = nativeDialog->selectedUrls().at(0); if (urlInfo(selectedUrl).isValid) { // validate directory first to populate m_projectName and m_projectManager validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); validateOpenUrl(selectedUrl); return true; } } else { return false; } } } int OpenProjectDialog::exec() { if (nativeDialog && !execNativeDialog()) { reject(); return QDialog::Rejected; } return KAssistantDialog::exec(); } void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); if (!nativeDialog) { openPageWidget->setUrl(sourcePageWidget->workingDir()); } } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { URLInfo urlInfo = ::urlInfo(url_); const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); // openPage is used only in KDE if (openPage) { if ( urlInfo.isValid ) { // reset header openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); } else { // report error KColorScheme scheme(palette().currentColorGroup()); const QString errorMsg = i18n("Selected URL is invalid"); openPage->setHeader(QStringLiteral("%2") .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) ); setAppropriate( projectInfoPage, false ); setAppropriate( openPage, true ); setValid( openPage, false ); return; } } m_selected = url; if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) { m_urlIsDirectory = urlInfo.isDir; setAppropriate( projectInfoPage, true ); m_url = url; if( !urlInfo.isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } auto* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); // clear the filelist m_fileList.clear(); if( urlInfo.isDir ) { // If a dir was selected fetch all files in it KIO::ListJob* job = KIO::listDir( m_url ); connect( job, &KIO::ListJob::entries, this, &OpenProjectDialog::storeFileList); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } else { // Else we'll just take the given file m_fileList << url.fileName(); } // Now find a manager for the file(s) in our filelist. QVector choices; for (const auto& file : qAsConst(m_fileList)) { auto plugins = projectManagerForFile(file); if ( plugins.contains(QStringLiteral("")) ) { plugins.removeAll(QStringLiteral("")); - choices.append({i18n("Open existing file \"%1\"", file), QStringLiteral(""), QString()}); + choices.append({i18nc("@item:inlistbox", "Open existing file \"%1\"", file), QStringLiteral(""), QString()}); } choices.reserve(choices.size() + plugins.size()); for (const auto& plugin : qAsConst(plugins)) { auto meta = m_projectPlugins.value(plugin); choices.append({file + QLatin1String(" (") + plugin + QLatin1Char(')'), meta.pluginId(), meta.iconName(), file}); } } // add managers that work in any case (e.g. KDevGenericManager) choices.reserve(choices.size() + m_genericProjectPlugins.size()); for (const auto& plugin : qAsConst(m_genericProjectPlugins)) { qCDebug(SHELL) << plugin; auto meta = m_projectPlugins.value(plugin); choices.append({plugin, meta.pluginId(), meta.iconName()}); } page->populateProjectFileCombo(choices); } m_url.setPath( m_url.path() + QLatin1Char('/') + m_url.fileName() + QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension() ); } else { setAppropriate( projectInfoPage, false ); m_url = url; m_urlIsDirectory = false; } validateProjectInfo(); setValid( openPage, true ); } QStringList OpenProjectDialog::projectManagerForFile(const QString& file) const { QStringList ret; for (auto it = m_projectFilters.begin(), end = m_projectFilters.end(); it != end; ++it) { const QString& manager = it.key(); for (const QString& filterexp : it.value()) { QRegExp exp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ); if ( exp.exactMatch(file) ) { ret.append(manager); } } } if ( file.endsWith(ShellExtension::getInstance()->projectFileExtension()) ) { ret.append(QStringLiteral("")); } return ret; } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager, const QString & fileName ) { m_projectManager = manager; if ( m_urlIsDirectory ) { m_selected = m_url.resolved( QUrl(QLatin1String("./") + fileName) ); } validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() const { return m_url; } QUrl OpenProjectDialog::selectedUrl() const { return m_selected; } QString OpenProjectDialog::projectName() const { return m_projectName; } QString OpenProjectDialog::projectManager() const { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { for (const KIO::UDSEntry& entry : list) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/kdevplatform/shell/partcontroller.cpp b/kdevplatform/shell/partcontroller.cpp index 27fd29f86f..4447310466 100644 --- a/kdevplatform/shell/partcontroller.cpp +++ b/kdevplatform/shell/partcontroller.cpp @@ -1,262 +1,262 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "partcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "textdocument.h" #include "debug.h" #include "uicontroller.h" #include "mainwindow.h" #include #include #include #include namespace KDevelop { class PartControllerPrivate { public: explicit PartControllerPrivate(Core* core) : m_core(core) {} bool m_showTextEditorStatusBar = false; QString m_editor; QStringList m_textTypes; Core* const m_core; }; PartController::PartController(Core *core, QWidget *toplevel) : IPartController(toplevel) , d_ptr(new PartControllerPrivate(core)) { setObjectName(QStringLiteral("PartController")); //Cache this as it is too expensive when creating parts // KConfig * config = Config::standard(); // config->setGroup( "General" ); // // d->m_textTypes = config->readEntry( "TextTypes", QStringList() ); // // config ->setGroup( "Editor" ); // d->m_editor = config->readPathEntry( "EmbeddedKTextEditor", QString() ); // required early because some actions are checkable and need to be initialized loadSettings(false); if (!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } PartController::~PartController() = default; bool PartController::showTextEditorStatusBar() const { Q_D(const PartController); return d->m_showTextEditorStatusBar; } void PartController::setShowTextEditorStatusBar(bool show) { Q_D(PartController); if (d->m_showTextEditorStatusBar == show) return; d->m_showTextEditorStatusBar = show; // update const auto areas = Core::self()->uiControllerInternal()->allAreas(); for (Sublime::Area* area : areas) { const auto views = area->views(); for (Sublime::View* view : views) { if (!view->hasWidget()) continue; auto textView = qobject_cast(view->widget()); if (textView) { textView->setStatusBarEnabled(show); } } } // also notify active view that it should update the "view status" auto* textView = qobject_cast(Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()); if (textView) { emit textView->statusChanged(textView); } } //MOVE BACK TO DOCUMENTCONTROLLER OR MULTIBUFFER EVENTUALLY bool PartController::isTextType(const QMimeType& mimeType) { Q_D(PartController); bool isTextType = false; if (d->m_textTypes.contains(mimeType.name())) { isTextType = true; } // is this regular text - open in editor return ( isTextType || mimeType.inherits(QStringLiteral("text/plain")) || mimeType.inherits(QStringLiteral("text/html")) || mimeType.inherits(QStringLiteral("application/x-zerosize"))); } KTextEditor::Editor* PartController::editorPart() const { return KTextEditor::Editor::instance(); } KTextEditor::Document* PartController::createTextPart() { return editorPart()->createDocument(this); } KParts::Part* PartController::createPart( const QString & mimeType, const QString & partType, const QString & className, const QString & preferredName ) { KPluginFactory * editorFactory = findPartFactory( mimeType, partType, preferredName ); if ( !className.isEmpty() && editorFactory ) { return editorFactory->create( nullptr, this, className); } return nullptr; } bool PartController::canCreatePart(const QUrl& url) { if (!url.isValid()) return false; QString mimeType; if ( url.isEmpty() ) mimeType = QStringLiteral("text/plain"); else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KService::List offers = KMimeTypeTrader::self()->query( mimeType, QStringLiteral("KParts/ReadOnlyPart") ); return offers.count() > 0; } KParts::Part* PartController::createPart( const QUrl & url, const QString& preferredPart ) { qCDebug(SHELL) << "creating part with url" << url << "and pref part:" << preferredPart; QString mimeType; if ( url.isEmpty() ) //create a part for empty text file mimeType = QStringLiteral("text/plain"); else if ( !url.isValid() ) return nullptr; else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KParts::Part* part = createPart( mimeType, preferredPart ); if( part ) { // only ReadOnlyParts are supported by PartController static_cast(part)->openUrl(url); return part; } return nullptr; } void PartController::loadSettings( bool projectIsLoaded ) { Q_D(PartController); Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); d->m_showTextEditorStatusBar = cg.readEntry("ShowTextEditorStatusBar", false); } void PartController::saveSettings( bool projectIsLoaded ) { Q_D(PartController); Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); cg.writeEntry("ShowTextEditorStatusBar", d->m_showTextEditorStatusBar); } void PartController::initialize() { } void PartController::cleanup() { saveSettings(false); } void PartController::setupActions() { Q_D(PartController); KActionCollection* actionCollection = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = KStandardAction::showStatusbar(this, SLOT(setShowTextEditorStatusBar(bool)), actionCollection); - action->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); + action->setWhatsThis(i18nc("@info:whatsthis", "Use this command to show or hide the view's statusbar.")); action->setChecked(showTextEditorStatusBar()); } } diff --git a/kdevplatform/shell/partdocument.cpp b/kdevplatform/shell/partdocument.cpp index 5c011c6734..3a0a740a0d 100644 --- a/kdevplatform/shell/partdocument.cpp +++ b/kdevplatform/shell/partdocument.cpp @@ -1,244 +1,244 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "partdocument.h" #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "partcontroller.h" namespace KDevelop { class PartDocumentPrivate { public: explicit PartDocumentPrivate(const QString& preferredPart) : preferredPart(preferredPart) {} QMap partForView; const QString preferredPart; }; PartDocument::PartDocument(const QUrl& url, KDevelop::ICore* core, const QString& preferredPart) : Sublime::UrlDocument(core->uiController()->controller(), url) , KDevelop::IDocument(core) , d_ptr(new PartDocumentPrivate(preferredPart)) { } PartDocument::~PartDocument() = default; QWidget *PartDocument::createViewWidget(QWidget* /*parent*/) { Q_D(PartDocument); KParts::Part *part = Core::self()->partControllerInternal()->createPart(url(), d->preferredPart); if( part ) { Core::self()->partController()->addPart(part); QWidget *w = part->widget(); d->partForView[w] = part; return w; } return nullptr; } KParts::Part *PartDocument::partForView(QWidget *view) const { Q_D(const PartDocument); return d->partForView[view]; } //KDevelop::IDocument implementation QMimeType PartDocument::mimeType() const { return QMimeDatabase().mimeTypeForUrl(url()); } KTextEditor::Document *PartDocument::textDocument() const { return nullptr; } bool PartDocument::isActive() const { const auto activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView(); if (!activeView) { return false; } return activeView->document() == this; } bool PartDocument::save(DocumentSaveMode /*mode*/) { //part document is read-only so do nothing here return true; } bool PartDocument::askForCloseFeedback() { int code = -1; if (state() == IDocument::Modified) { code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The document \"%1\" has unsaved changes. Would you like to save them?", url().toLocalFile()), - i18n("Close Document")); + i18nc("@title:window", "Close Document")); /// @todo Is this behavior right? } else if (state() == IDocument::DirtyAndModified) { code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The document \"%1\" has unsaved changes and was modified by an external process.\n" "Do you want to override the external changes?", url().toLocalFile()), - i18n("Close Document")); + i18nc("@title:window", "Close Document")); } if (code >= 0) { if (code == KMessageBox::Yes) { if (!save(Default)) return false; } else if (code == KMessageBox::Cancel) { return false; } } return true; } bool PartDocument::close(DocumentSaveMode mode) { Q_D(PartDocument); if (!(mode & Discard)) { if (mode & Silent) { if (!save(mode)) return false; } else { if( !askForCloseFeedback() ) return false; } } //close all views and then delete ourself closeViews(); for (KParts::Part* part : qAsConst(d->partForView)) { part->deleteLater(); } // The document will be deleted automatically if there are no views left return true; } bool PartDocument::closeDocument(bool silent) { return close(silent ? Silent : Default); } void PartDocument::reload() { //part document is read-only so do nothing here } IDocument::DocumentState PartDocument::state() const { return Clean; } void PartDocument::activate(Sublime::View *activeView, KParts::MainWindow *mainWindow) { Q_UNUSED(mainWindow); KParts::Part *part = partForView(activeView->widget()); if (Core::self()->partController()->activePart() != part) Core::self()->partController()->setActivePart(part); notifyActivated(); } KTextEditor::Cursor KDevelop::PartDocument::cursorPosition() const { return KTextEditor::Cursor::invalid(); } void PartDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { //do nothing here Q_UNUSED(cursor); } void PartDocument::setTextSelection(const KTextEditor::Range &range) { Q_UNUSED(range); } QUrl PartDocument::url() const { return Sublime::UrlDocument::url(); } void PartDocument::setUrl(const QUrl& newUrl) { Sublime::UrlDocument::setUrl(newUrl); if(!prettyName().isEmpty()) setTitle(prettyName()); notifyUrlChanged(); } void PartDocument::setPrettyName(const QString& name) { KDevelop::IDocument::setPrettyName(name); // Re-set the url, to trigger the whole chain if(!name.isEmpty()) setTitle(name); else setTitle(Core::self()->projectController()->prettyFileName(url(), KDevelop::IProjectController::FormatPlain)); } QMap PartDocument::partForView() const { Q_D(const PartDocument); return d->partForView; } void PartDocument::addPartForView(QWidget* w, KParts::Part* p) { Q_D(PartDocument); d->partForView[w]=p; } } diff --git a/kdevplatform/shell/problem.cpp b/kdevplatform/shell/problem.cpp index 5de404b1f8..6ecb3e07aa 100644 --- a/kdevplatform/shell/problem.cpp +++ b/kdevplatform/shell/problem.cpp @@ -1,230 +1,230 @@ /* * Copyright 2015 Laszlo Kis-Adam * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include namespace KDevelop { class DetectedProblemPrivate { public: DetectedProblemPrivate(const QString& pluginName) : m_pluginName(pluginName) , m_severity(KDevelop::IProblem::Error) , m_source(KDevelop::IProblem::Unknown) , m_finalLocationMode(KDevelop::IProblem::Range) { } QString m_description; QString m_explanation; const QString m_pluginName; KDevelop::IProblem::Severity m_severity; KDevelop::IProblem::Source m_source; KDevelop::DocumentRange m_range; QVector m_diagnostics; KDevelop::IProblem::FinalLocationMode m_finalLocationMode; }; DetectedProblem::DetectedProblem() : d_ptr(new DetectedProblemPrivate(i18n("Plugin"))) { } DetectedProblem::DetectedProblem(const QString& pluginName) : d_ptr(new DetectedProblemPrivate(pluginName)) { setSource(Plugin); } DetectedProblem::~DetectedProblem() { clearDiagnostics(); } IProblem::Source DetectedProblem::source() const { Q_D(const DetectedProblem); return d->m_source; } void DetectedProblem::setSource(Source source) { Q_D(DetectedProblem); d->m_source = source; } QString DetectedProblem::sourceString() const { Q_D(const DetectedProblem); switch(d->m_source) { case Unknown: return i18n("Unknown"); case Disk: return i18n("Disk"); case Preprocessor: return i18n("Preprocessor"); case Lexer: return i18n("Lexer"); case Parser: return i18n("Parser"); case DUChainBuilder: return i18n("DuchainBuilder"); case SemanticAnalysis: return i18n("Semantic analysis"); case ToDo: return i18n("Todo"); case Plugin: return d->m_pluginName; } return {}; } DocumentRange DetectedProblem::finalLocation() const { Q_D(const DetectedProblem); return d->m_range; } void DetectedProblem::setFinalLocation(const DocumentRange &location) { Q_D(DetectedProblem); d->m_range = location; } IProblem::FinalLocationMode DetectedProblem::finalLocationMode() const { Q_D(const DetectedProblem); return d->m_finalLocationMode; } void DetectedProblem::setFinalLocationMode(IProblem::FinalLocationMode mode) { Q_D(DetectedProblem); d->m_finalLocationMode = mode; } QString DetectedProblem::description() const { Q_D(const DetectedProblem); return d->m_description; } void DetectedProblem::setDescription(const QString &description) { Q_D(DetectedProblem); d->m_description = description; } QString DetectedProblem::explanation() const { Q_D(const DetectedProblem); return d->m_explanation; } void DetectedProblem::setExplanation(const QString &explanation) { Q_D(DetectedProblem); d->m_explanation = explanation; } IProblem::Severity DetectedProblem::severity() const { Q_D(const DetectedProblem); return d->m_severity; } void DetectedProblem::setSeverity(Severity severity) { Q_D(DetectedProblem); d->m_severity = severity; } QString DetectedProblem::severityString() const { Q_D(const DetectedProblem); QString s; switch(d->m_severity) { - case Hint: s = i18n("Hint"); break; - case Warning: s = i18n("Warning"); break; - case Error: s = i18n("Error"); break; + case Hint: s = i18nc("@item problem severity", "Hint"); break; + case Warning: s = i18nc("@item problem severity", "Warning"); break; + case Error: s = i18nc("@item problem severity", "Error"); break; default: break; } return s; } QVector DetectedProblem::diagnostics() const { Q_D(const DetectedProblem); return d->m_diagnostics; } void DetectedProblem::setDiagnostics(const QVector &diagnostics) { clearDiagnostics(); for (const Ptr& diagnostic : diagnostics) { addDiagnostic(diagnostic); } } void DetectedProblem::addDiagnostic(const Ptr &diagnostic) { Q_D(DetectedProblem); auto *dp = dynamic_cast(diagnostic.data()); Q_ASSERT(dp); d->m_diagnostics.push_back(diagnostic); } void DetectedProblem::clearDiagnostics() { Q_D(DetectedProblem); d->m_diagnostics.clear(); } QExplicitlySharedDataPointer DetectedProblem::solutionAssistant() const { return {}; } } diff --git a/kdevplatform/shell/progresswidget/progressdialog.cpp b/kdevplatform/shell/progresswidget/progressdialog.cpp index 3e262c4528..417e64101c 100644 --- a/kdevplatform/shell/progresswidget/progressdialog.cpp +++ b/kdevplatform/shell/progresswidget/progressdialog.cpp @@ -1,414 +1,414 @@ /** -*- c++ -*- * progressdialog.cpp * * Copyright (c) 2004 Till Adam , * David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "progressdialog.h" #include "progressmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { static const int MAX_LABEL_WIDTH = 650; class TransactionItem; TransactionItemView::TransactionItemView( QWidget *parent, const char *name ) : QScrollArea( parent ) { setObjectName(QString::fromUtf8(name)); setFrameStyle( NoFrame ); mBigBox = new QWidget( this ); auto layout = new QVBoxLayout(mBigBox); layout->setMargin(0); setWidget( mBigBox ); setWidgetResizable( true ); setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ); } TransactionItem *TransactionItemView::addTransactionItem( ProgressItem *item, bool first ) { auto *ti = new TransactionItem( mBigBox, item, first ); mBigBox->layout()->addWidget( ti ); resize( mBigBox->width(), mBigBox->height() ); return ti; } void TransactionItemView::resizeEvent ( QResizeEvent *event ) { // Tell the layout in the parent (progressdialog) that our size changed updateGeometry(); QSize sz = parentWidget()->sizeHint(); int currentWidth = parentWidget()->width(); // Don't resize to sz.width() every time when it only reduces a little bit if ( currentWidth < sz.width() || currentWidth > sz.width() + 100 ) { currentWidth = sz.width(); } parentWidget()->resize( currentWidth, sz.height() ); QScrollArea::resizeEvent( event ); } QSize TransactionItemView::sizeHint() const { return minimumSizeHint(); } QSize TransactionItemView::minimumSizeHint() const { int f = 2 * frameWidth(); // Make room for a vertical scrollbar in all cases, to avoid a horizontal one int vsbExt = verticalScrollBar()->sizeHint().width(); QSize sz( mBigBox->minimumSizeHint() ); sz.setWidth( sz.width() + f + vsbExt ); sz.setHeight( sz.height() + f ); return sz; } void TransactionItemView::slotItemCompleted(TransactionItem* item) { // If completed item is the first, hide separator line for the one that will become first now if (mBigBox->layout()->indexOf(item) == 0) { auto *secondItem = mBigBox->layout()->itemAt(1); if (secondItem) { static_cast(secondItem->widget())->hideHLine(); } } mBigBox->layout()->removeWidget(item); delete item; //This slot is called whenever a TransactionItem is deleted, so this is a //good place to call updateGeometry(), so our parent takes the new size //into account and resizes. updateGeometry(); } // ---------------------------------------------------------------------------- TransactionItem::TransactionItem( QWidget *parent, ProgressItem *item, bool first ) : QWidget( parent ), mCancelButton( nullptr ), mItem( item ) { auto vbox = new QVBoxLayout(this); vbox->setSpacing( 2 ); vbox->setMargin( 2 ); setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mFrame = new QFrame( this ); mFrame->setFrameShape( QFrame::HLine ); mFrame->setFrameShadow( QFrame::Raised ); mFrame->show(); vbox->setStretchFactor( mFrame, 3 ); vbox->addWidget( mFrame ); auto* h = new QWidget( this ); auto hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); vbox->addWidget( h ); mItemLabel = new QLabel( fontMetrics().elidedText( item->label(), Qt::ElideRight, MAX_LABEL_WIDTH ), h ); h->layout()->addWidget( mItemLabel ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mProgress = new QProgressBar( h ); hboxLayout->addWidget(mProgress); mProgress->setMaximum( 100 ); mProgress->setValue( item->progress() ); h->layout()->addWidget( mProgress ); if ( item->canBeCanceled() ) { mCancelButton = new QPushButton( QIcon::fromTheme( QStringLiteral("dialog-cancel") ), QString(), h ); hboxLayout->addWidget(mCancelButton); - mCancelButton->setToolTip( i18n( "Cancel this operation." ) ); + mCancelButton->setToolTip( i18nc("@info:tooltip", "Cancel this operation" ) ); connect ( mCancelButton, &QPushButton::clicked, this, &TransactionItem::slotItemCanceled); h->layout()->addWidget( mCancelButton ); } h = new QWidget( this ); hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); vbox->addWidget( h ); mItemStatus = new QLabel( h ); hboxLayout->addWidget(mItemStatus); mItemStatus->setTextFormat( Qt::RichText ); mItemStatus->setText( fontMetrics().elidedText( item->status(), Qt::ElideRight, MAX_LABEL_WIDTH ) ); h->layout()->addWidget( mItemStatus ); if ( first ) { hideHLine(); } } TransactionItem::~TransactionItem() { } void TransactionItem::hideHLine() { mFrame->hide(); } void TransactionItem::setProgress( int progress ) { mProgress->setValue( progress ); } void TransactionItem::setLabel( const QString &label ) { mItemLabel->setText( fontMetrics().elidedText( label, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setStatus( const QString &status ) { mItemStatus->setText( fontMetrics().elidedText( status, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setTotalSteps( int totalSteps ) { mProgress->setMaximum( totalSteps ); } void TransactionItem::slotItemCanceled() { if ( mItem ) { mItem->cancel(); } } void TransactionItem::addSubTransaction( ProgressItem *item ) { Q_UNUSED( item ); } // --------------------------------------------------------------------------- ProgressDialog::ProgressDialog( QWidget *alignWidget, QWidget *parent, const char *name ) : OverlayWidget( alignWidget, parent, name ), mWasLastShown( false ) { setAutoFillBackground( true ); mScrollView = new TransactionItemView( this, "ProgressScrollView" ); layout()->addWidget( mScrollView ); // No more close button for now, since there is no more autoshow /* QVBox* rightBox = new QVBox( this ); QToolButton* pbClose = new QToolButton( rightBox ); pbClose->setAutoRaise(true); pbClose->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); pbClose->setFixedSize( 16, 16 ); pbClose->setIcon( KIconLoader::global()->loadIconSet( "window-close", KIconLoader::Small, 14 ) ); pbClose->setToolTip( i18n( "Hide detailed progress window" ) ); connect(pbClose, SIGNAL(clicked()), this, SLOT(slotClose())); QWidget* spacer = new QWidget( rightBox ); // don't let the close button take up all the height rightBox->setStretchFactor( spacer, 100 ); */ /* * Get the singleton ProgressManager item which will inform us of * appearing and vanishing items. */ ProgressManager *pm = ProgressManager::instance(); connect ( pm, &ProgressManager::progressItemAdded, this, &ProgressDialog::slotTransactionAdded ); connect ( pm, &ProgressManager::progressItemCompleted, this, &ProgressDialog::slotTransactionCompleted ); connect ( pm, &ProgressManager::progressItemProgress, this, &ProgressDialog::slotTransactionProgress ); connect ( pm, &ProgressManager::progressItemStatus, this, &ProgressDialog::slotTransactionStatus ); connect ( pm, &ProgressManager::progressItemLabel, this, &ProgressDialog::slotTransactionLabel ); connect ( pm, &ProgressManager::progressItemUsesBusyIndicator, this, &ProgressDialog::slotTransactionUsesBusyIndicator ); connect ( pm, &ProgressManager::showProgressDialog, this, &ProgressDialog::slotShow ); } void ProgressDialog::closeEvent( QCloseEvent *e ) { e->accept(); hide(); } /* * Destructor */ ProgressDialog::~ProgressDialog() { // no need to delete child widgets. } void ProgressDialog::slotTransactionAdded( ProgressItem *item ) { if ( item->parent() ) { const auto parentItemIt = mTransactionsToListviewItems.constFind(item->parent()); if (parentItemIt != mTransactionsToListviewItems.constEnd()) { TransactionItem* parent = *parentItemIt; parent->addSubTransaction( item ); } } else { const bool first = mTransactionsToListviewItems.empty(); TransactionItem *ti = mScrollView->addTransactionItem( item, first ); if ( ti ) { mTransactionsToListviewItems.insert( item, ti ); } if ( first && mWasLastShown ) { QTimer::singleShot( 1000, this, &ProgressDialog::slotShow ); } } } void ProgressDialog::slotTransactionCompleted( ProgressItem *item ) { const auto itemIt = mTransactionsToListviewItems.find(item); if (itemIt != mTransactionsToListviewItems.end()) { TransactionItem* ti = *itemIt; mTransactionsToListviewItems.erase(itemIt); ti->setItemComplete(); QTimer::singleShot( 3000, mScrollView, [=] { mScrollView->slotItemCompleted(ti); } ); } // This was the last item, hide. if ( mTransactionsToListviewItems.empty() ) { QTimer::singleShot( 3000, this, &ProgressDialog::slotHide ); } } void ProgressDialog::slotTransactionCanceled( ProgressItem * ) { } void ProgressDialog::slotTransactionProgress( ProgressItem *item, unsigned int progress ) { const auto itemIt = mTransactionsToListviewItems.constFind(item); if (itemIt != mTransactionsToListviewItems.constEnd()) { TransactionItem* ti = *itemIt; ti->setProgress( progress ); } } void ProgressDialog::slotTransactionStatus( ProgressItem *item, const QString &status ) { const auto itemIt = mTransactionsToListviewItems.constFind(item); if (itemIt != mTransactionsToListviewItems.constEnd()) { TransactionItem* ti = *itemIt; ti->setStatus( status ); } } void ProgressDialog::slotTransactionLabel( ProgressItem *item, const QString &label ) { const auto itemIt = mTransactionsToListviewItems.constFind(item); if (itemIt != mTransactionsToListviewItems.constEnd()) { TransactionItem* ti = *itemIt; ti->setLabel( label ); } } void ProgressDialog::slotTransactionUsesBusyIndicator( KDevelop::ProgressItem *item, bool value ) { const auto itemIt = mTransactionsToListviewItems.constFind(item); if (itemIt != mTransactionsToListviewItems.constEnd()) { TransactionItem* ti = *itemIt; if ( value ) { ti->setTotalSteps( 0 ); } else { ti->setTotalSteps( 100 ); } } } void ProgressDialog::slotShow() { setVisible( true ); } void ProgressDialog::slotHide() { // check if a new item showed up since we started the timer. If not, hide if ( mTransactionsToListviewItems.isEmpty() ) { setVisible( false ); } mWasLastShown = false; } void ProgressDialog::slotClose() { mWasLastShown = false; setVisible( false ); } void ProgressDialog::setVisible( bool b ) { OverlayWidget::setVisible( b ); emit visibilityChanged( b ); } void ProgressDialog::slotToggleVisibility() { /* Since we are only hiding with a timeout, there is a short period of * time where the last item is still visible, but clicking on it in * the statusbarwidget should not display the dialog, because there * are no items to be shown anymore. Guard against that. */ mWasLastShown = isHidden(); if ( !isHidden() || !mTransactionsToListviewItems.isEmpty() ) { setVisible( isHidden() ); } } } diff --git a/kdevplatform/shell/progresswidget/progressmanager.cpp b/kdevplatform/shell/progresswidget/progressmanager.cpp index 4b76e8c36a..7463ae6990 100644 --- a/kdevplatform/shell/progresswidget/progressmanager.cpp +++ b/kdevplatform/shell/progresswidget/progressmanager.cpp @@ -1,262 +1,262 @@ /* progressmanager.cpp This file is part of libkdepim. Copyright (c) 2004 Till Adam This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "progressmanager.h" #include "debug.h" #include namespace KDevelop { unsigned int KDevelop::ProgressManager::uID = 42; ProgressItem::ProgressItem( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) : mId( id ), mLabel( label ), mStatus( status ), mParent( parent ), mCanBeCanceled( canBeCanceled ), mProgress( 0 ), mTotal( 0 ), mCompleted( 0 ), mWaitingForKids( false ), mCanceled( false ), mUsesCrypto( usesCrypto ), mUsesBusyIndicator( false ), mCompletedCalled( false ) { } ProgressItem::~ProgressItem() { } void ProgressItem::setComplete() { // qCDebug(SHELL) << label(); if ( mChildren.isEmpty() ) { if ( mCompletedCalled ) return; if ( !mCanceled ) { setProgress( 100 ); } mCompletedCalled = true; if ( parent() ) { parent()->removeChild( this ); } emit progressItemCompleted( this ); } else { mWaitingForKids = true; } } void ProgressItem::addChild( ProgressItem *kiddo ) { mChildren.insert( kiddo, true ); } void ProgressItem::removeChild( ProgressItem *kiddo ) { if ( mChildren.isEmpty() ) { mWaitingForKids = false; return; } if ( mChildren.remove( kiddo ) == 0 ) { // do nothing if the specified item is not in the map return; } // in case we were waiting for the last kid to go away, now is the time if ( mChildren.count() == 0 && mWaitingForKids ) { emit progressItemCompleted( this ); } } void ProgressItem::cancel() { if ( mCanceled || !mCanBeCanceled ) { return; } qCDebug(SHELL) << label(); mCanceled = true; // Cancel all children. const QList kids = mChildren.keys(); for (ProgressItem* kid : kids) { if ( kid->canBeCanceled() ) { kid->cancel(); } } - setStatus( i18n( "Aborting..." ) ); + setStatus( i18nc("@info", "Aborting..." ) ); emit progressItemCanceled( this ); } void ProgressItem::setProgress( unsigned int v ) { mProgress = v; // qCDebug(SHELL) << label() << " :" << v; emit progressItemProgress( this, mProgress ); } void ProgressItem::setLabel( const QString &v ) { mLabel = v; emit progressItemLabel( this, mLabel ); } void ProgressItem::setStatus( const QString &v ) { mStatus = v; emit progressItemStatus( this, mStatus ); } void ProgressItem::setUsesCrypto( bool v ) { mUsesCrypto = v; emit progressItemUsesCrypto( this, v ); } void ProgressItem::setUsesBusyIndicator( bool useBusyIndicator ) { mUsesBusyIndicator = useBusyIndicator; emit progressItemUsesBusyIndicator( this, useBusyIndicator ); } // ====================================== struct ProgressManagerPrivate { ProgressManager instance; }; Q_GLOBAL_STATIC( ProgressManagerPrivate, progressManagerPrivate ) ProgressManager::ProgressManager() : QObject() { } ProgressManager::~ProgressManager() {} ProgressManager *ProgressManager::instance() { return progressManagerPrivate.isDestroyed() ? nullptr : &progressManagerPrivate->instance ; } ProgressItem *ProgressManager::createProgressItemImpl( ProgressItem *parent, const QString &id, const QString &label, const QString &status, bool cancellable, bool usesCrypto ) { ProgressItem *t = nullptr; if ( !mTransactions.value( id ) ) { t = new ProgressItem ( parent, id, label, status, cancellable, usesCrypto ); mTransactions.insert( id, t ); if ( parent ) { ProgressItem *p = mTransactions.value( parent->id() ); if ( p ) { p->addChild( t ); } } // connect all signals connect ( t, &ProgressItem::progressItemCompleted, this, &ProgressManager::slotTransactionCompleted ); connect ( t, &ProgressItem::progressItemProgress, this, &ProgressManager::progressItemProgress ); connect ( t, &ProgressItem::progressItemAdded, this, &ProgressManager::progressItemAdded ); connect ( t, &ProgressItem::progressItemCanceled, this, &ProgressManager::progressItemCanceled ); connect ( t, &ProgressItem::progressItemStatus, this, &ProgressManager::progressItemStatus ); connect ( t, &ProgressItem::progressItemLabel, this, &ProgressManager::progressItemLabel ); connect ( t, &ProgressItem::progressItemUsesCrypto, this, &ProgressManager::progressItemUsesCrypto ); connect ( t, &ProgressItem::progressItemUsesBusyIndicator, this, &ProgressManager::progressItemUsesBusyIndicator ); emit progressItemAdded( t ); } else { // Hm, is this what makes the most sense? t = mTransactions.value( id ); } return t; } ProgressItem *ProgressManager::createProgressItemImpl( const QString &parent, const QString &id, const QString &label, const QString &status, bool canBeCanceled, bool usesCrypto ) { ProgressItem *p = mTransactions.value( parent ); return createProgressItemImpl( p, id, label, status, canBeCanceled, usesCrypto ); } void ProgressManager::emitShowProgressDialogImpl() { emit showProgressDialog(); } // slots void ProgressManager::slotTransactionCompleted( ProgressItem *item ) { mTransactions.remove( item->id() ); emit progressItemCompleted( item ); } void ProgressManager::slotStandardCancelHandler( ProgressItem *item ) { item->setComplete(); } ProgressItem *ProgressManager::singleItem() const { ProgressItem *item = nullptr; for (ProgressItem* transactionItem : mTransactions) { // No single item for progress possible, as one of them is a busy indicator one. if (transactionItem->usesBusyIndicator()) return nullptr; if (!transactionItem->parent()) { // if it's a top level one, only those count if ( item ) { return nullptr; // we found more than one } else { item = transactionItem; } } } return item; } void ProgressManager::slotAbortAll() { QHashIterator it(mTransactions); while (it.hasNext()) { it.next(); it.value()->cancel(); } } } // namespace diff --git a/kdevplatform/shell/progresswidget/statusbarprogresswidget.cpp b/kdevplatform/shell/progresswidget/statusbarprogresswidget.cpp index 719456b86d..91202fe0a5 100644 --- a/kdevplatform/shell/progresswidget/statusbarprogresswidget.cpp +++ b/kdevplatform/shell/progresswidget/statusbarprogresswidget.cpp @@ -1,372 +1,372 @@ /* statusbarprogresswidget.cpp (C) 2004 Till Adam Don Sanders David Faure Copyright 2004 David Faure Includes StatusbarProgressWidget which is based on KIOLittleProgressDlg by Matt Koss KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2 or above, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "statusbarprogresswidget.h" #include "progressdialog.h" #include "progressmanager.h" #ifdef Q_OS_OSX #include "../macdockprogressview.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; //----------------------------------------------------------------------------- StatusbarProgressWidget::StatusbarProgressWidget( ProgressDialog* progressDialog, QWidget* parent, bool button ) : QFrame( parent ), mCurrentItem( nullptr ), mProgressDialog( progressDialog ), mDelayTimer( nullptr ), mCleanTimer( nullptr ) { m_bShowButton = button; #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) int w = fontMetrics().horizontalAdvance(QStringLiteral(" 999.9 kB/s 00:00:01 ")) + 8; #else int w = fontMetrics().width( QStringLiteral(" 999.9 kB/s 00:00:01 ") ) + 8; #endif box = new QHBoxLayout( this ); box->setMargin(0); box->setSpacing(0); stack = new QStackedWidget( this ); m_pButton = new QToolButton( this ); m_pButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); QIcon smallIcon = QIcon::fromTheme( QStringLiteral("go-up") ); if ( smallIcon.isNull() ) { // this can happen everywhere but in particular with a standard build on OS X. // QToolButtons won't be visible without an icon, so fall back to showing a Qt::UpArrow. m_pButton->setArrowType( Qt::UpArrow ); } else { m_pButton->setIcon( smallIcon ); } m_pButton->setAutoRaise(true); QSize iconSize = m_pButton->iconSize(); m_pProgressBar = new QProgressBar( this ); m_pProgressBar->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); m_pProgressBar->installEventFilter( this ); m_pProgressBar->setMinimumWidth( w ); m_pProgressBar->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); // Determine maximumHeight from the progressbar's height and scale the icon. // This operation is style specific and cannot infer the style in use // from Q_OS_??? because users can have started us using the -style option // (or even be using an unexpected QPA). // In most cases, maximumHeight should be set to fontMetrics().height() + 2 // (Breeze, Oxygen, Fusion, Windows, QtCurve etc.); this corresponds to the actual // progressbar height plus a 1 pixel margin above and below. int maximumHeight = m_pProgressBar->fontMetrics().height() + 2; const bool isMacWidgetStyle = QApplication::style()->objectName() == QLatin1String( "macintosh" ); if ( isMacWidgetStyle && !smallIcon.isNull() ) { // QProgressBar height is fixed with the macintosh native widget style // and alignment with m_pButton is tricky. Sizing the icon to maximumHeight // gives a button that is slightly too high and not perfectly // aligned. Annoyingly that doesn't improve by calling setMaximumHeight() // which even causes the button to change shape. So we use a "flat" button, // an invisible outline which is more in line with platform practices anyway. maximumHeight = m_pProgressBar->sizeHint().height(); iconSize.scale( maximumHeight, maximumHeight, Qt::KeepAspectRatio ); } else { // The icon is scaled to maximumHeight but with 1 pixel margins on each side // because it will be in a visible button. iconSize.scale( maximumHeight - 2, maximumHeight - 2, Qt::KeepAspectRatio ); // additional adjustments: m_pButton->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); } stack->setMaximumHeight( maximumHeight ); m_pButton->setIconSize( iconSize ); box->addWidget( m_pButton ); - m_pButton->setToolTip( i18n("Open detailed progress dialog") ); + m_pButton->setToolTip( i18nc("@info:tooltip", "Open detailed progress dialog") ); box->addWidget( stack ); stack->insertWidget( 1, m_pProgressBar ); if (m_bShowButton) { // create an empty, inactive QToolButton that's as high as m_pButton but only 1 pixel wide // this will act as a placeholder when the widget is invisible. m_pPlaceHolder.button = new QToolButton(this); m_pPlaceHolder.button->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); m_pPlaceHolder.button->setMinimumHeight(m_pButton->minimumSizeHint().height()); m_pPlaceHolder.button->setMaximumWidth(1); m_pPlaceHolder.button->setAutoRaise(true); m_pPlaceHolder.button->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); m_pPlaceHolder.button->setEnabled(false); m_pPlaceHolder.button->installEventFilter( this ); // the placeholder button should not go into the stack to avoid misalignment box->addWidget( m_pPlaceHolder.button ); m_pPlaceHolder.button->hide(); } else { // when the widget doesn't show m_pButton we can use a QLabel as the placeholder. m_pPlaceHolder.label = new QLabel( QString(), this ); m_pPlaceHolder.label->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); m_pPlaceHolder.label->setAlignment( Qt::AlignHCenter ); m_pPlaceHolder.label->installEventFilter( this ); m_pPlaceHolder.label->setMinimumWidth( w ); m_pPlaceHolder.label->setMaximumHeight( maximumHeight ); stack->insertWidget( 2, m_pPlaceHolder.label ); } setMinimumWidth( minimumSizeHint().width() ); mode = None; setMode(); connect( m_pButton, &QPushButton::clicked, progressDialog, &ProgressDialog::slotToggleVisibility ); connect ( ProgressManager::instance(), &ProgressManager::progressItemAdded, this, &StatusbarProgressWidget::slotProgressItemAdded ); connect ( ProgressManager::instance(), &ProgressManager::progressItemCompleted, this, &StatusbarProgressWidget::slotProgressItemCompleted ); connect ( ProgressManager::instance(), &ProgressManager::progressItemUsesBusyIndicator, this, &StatusbarProgressWidget::updateBusyMode ); connect ( progressDialog, &ProgressDialog::visibilityChanged, this, &StatusbarProgressWidget::slotProgressDialogVisible ); mDelayTimer = new QTimer( this ); mDelayTimer->setSingleShot( true ); mDelayTimer->setInterval(1000); connect ( mDelayTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotShowItemDelayed ); mCleanTimer = new QTimer( this ); mCleanTimer->setSingleShot( true ); mCleanTimer->setInterval(5000); connect ( mCleanTimer, &QTimer::timeout, this, &StatusbarProgressWidget::slotClean ); } // There are three cases: no progressitem, one progressitem (connect to it directly), // or many progressitems (display busy indicator). Let's call them 0,1,N. // In slot..Added we can only end up in 1 or N. // In slot..Removed we can end up in 0, 1, or we can stay in N if we were already. void StatusbarProgressWidget::updateBusyMode() { connectSingleItem(); // if going to 1 item if (!mDelayTimer->isActive()) mDelayTimer->start(); } void StatusbarProgressWidget::slotProgressItemAdded( ProgressItem *item ) { if ( item->parent() ) return; // we are only interested in top level items updateBusyMode(); } void StatusbarProgressWidget::slotProgressItemCompleted( ProgressItem *item ) { if ( item->parent() ) { item->deleteLater(); item = nullptr; return; // we are only interested in top level items } bool itemUsesBusyIndicator = item->usesBusyIndicator(); item->deleteLater(); item = nullptr; connectSingleItem(); // if going back to 1 item if ( ProgressManager::instance()->isEmpty() ) { // No item // If completed item uses busy indicator and progress manager doesn't have any // other items, then we should set it progress to 100% to indicate finishing. // Without this fix we will show busy indicator for already finished item // for next 5s. if ( itemUsesBusyIndicator ) { activateSingleItemMode( 100 ); } // Done. In 5s the progress-widget will close, then we can clean up the statusbar mCleanTimer->start(); } else if ( mCurrentItem ) { // Exactly one item activateSingleItemMode(); } } void StatusbarProgressWidget::connectSingleItem() { if ( mCurrentItem ) { disconnect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); mCurrentItem = nullptr; } mCurrentItem = ProgressManager::instance()->singleItem(); if ( mCurrentItem ) { connect ( mCurrentItem, &ProgressItem::progressItemProgress, this, &StatusbarProgressWidget::slotProgressItemProgress ); } } void StatusbarProgressWidget::activateSingleItemMode() { activateSingleItemMode( mCurrentItem->progress() ); } void StatusbarProgressWidget::activateSingleItemMode( unsigned int progress ) { m_pProgressBar->setMaximum( 100 ); m_pProgressBar->setValue( progress ); m_pProgressBar->setTextVisible( true ); #ifdef Q_OS_OSX MacDockProgressView::setRange( 0, 100 ); MacDockProgressView::setProgress( progress ); #endif } void StatusbarProgressWidget::slotShowItemDelayed() { bool noItems = ProgressManager::instance()->isEmpty(); if ( mCurrentItem ) { activateSingleItemMode(); } else if ( !noItems ) { // N items m_pProgressBar->setMaximum( 0 ); m_pProgressBar->setTextVisible( false ); #ifdef Q_OS_OSX MacDockProgressView::setRange( 0, 0 ); MacDockProgressView::setProgress( 0 ); #endif } if ( !noItems && mode == None ) { mode = Progress; setMode(); } } void StatusbarProgressWidget::slotProgressItemProgress( ProgressItem *item, unsigned int value ) { Q_ASSERT( item == mCurrentItem); // the only one we should be connected to Q_UNUSED( item ); m_pProgressBar->setValue( value ); #ifdef Q_OS_OSX MacDockProgressView::setProgress( value ); #endif } void StatusbarProgressWidget::setMode() { switch ( mode ) { case None: m_pButton->hide(); if ( m_bShowButton ) { // show the empty button in order to make the status bar look better m_pPlaceHolder.button->show(); } else { // show the empty label in order to make the status bar look better stack->setCurrentWidget( m_pPlaceHolder.label ); } m_pProgressBar->hide(); stack->show(); #ifdef Q_OS_OSX MacDockProgressView::setProgressVisible( false ); #endif break; case Progress: stack->show(); m_pProgressBar->show(); stack->setCurrentWidget( m_pProgressBar ); if ( m_bShowButton ) { m_pButton->show(); m_pPlaceHolder.button->hide(); } #ifdef Q_OS_OSX MacDockProgressView::setProgressVisible( true ); #endif break; } } void StatusbarProgressWidget::slotClean() { // check if a new item showed up since we started the timer. If not, clear if ( ProgressManager::instance()->isEmpty() ) { m_pProgressBar->setValue( 0 ); //m_pPlaceHolder.label->clear(); mode = None; setMode(); } } bool StatusbarProgressWidget::eventFilter(QObject* object, QEvent* ev) { if ( ev->type() == QEvent::MouseButtonPress ) { auto* e = static_cast(ev); if ( e->button() == Qt::LeftButton && mode != None ) { // toggle view on left mouse button // Consensus seems to be that we should show/hide the fancy dialog when the user // clicks anywhere in the small one. mProgressDialog->slotToggleVisibility(); return true; } } return QFrame::eventFilter(object, ev); } void StatusbarProgressWidget::slotProgressDialogVisible( bool b ) { // Update the hide/show button when the detailed one is shown/hidden if ( b ) { m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-down") ) ); - m_pButton->setToolTip( i18n("Hide detailed progress window") ); + m_pButton->setToolTip( i18nc("@info:tooltip", "Hide detailed progress window") ); setMode(); } else { m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-up") ) ); - m_pButton->setToolTip( i18n("Show detailed progress window") ); + m_pButton->setToolTip( i18nc("@info:tooltip", "Show detailed progress window") ); } } diff --git a/kdevplatform/shell/projectcontroller.cpp b/kdevplatform/shell/projectcontroller.cpp index ebf29c321c..37b7f13fd4 100644 --- a/kdevplatform/shell/projectcontroller.cpp +++ b/kdevplatform/shell/projectcontroller.cpp @@ -1,1384 +1,1384 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" // TODO: Should get rid off this include (should depend on IProject only) #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include "sessioncontroller.h" #include "session.h" #include "debug.h" namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentProjectsAction; Core* const m_core; // IProject* m_currentProject; ProjectModel* const model; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened ProjectController* const q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller ProjectChangesModel* m_changesModel = nullptr; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate(Core* core, ProjectController* p) : m_core(core) , model(new ProjectModel()) , dialog(nullptr) , q(p) , buildset(nullptr) , m_foundProjectFile(false) , m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; auto* proj = qobject_cast(obj); if( !proj ) return; auto cfgDlg = new KDevelop::ConfigDialog(m_core->uiController()->activeMainWindow()); cfgDlg->setAttribute(Qt::WA_DeleteOnClose); cfgDlg->setModal(true); QVector configPages; ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; const auto plugins = findPluginsForProject(proj); for (IPlugin* plugin : plugins) { const int perProjectConfigPagesCount = plugin->perProjectConfigPages(); configPages.reserve(configPages.size() + perProjectConfigPagesCount); for (int i = 0; i < perProjectConfigPagesCount; ++i) { configPages.append(plugin->perProjectConfigPage(i, options, cfgDlg)); } } std::sort(configPages.begin(), configPages.end(), [](const ConfigPage* a, const ConfigPage* b) { return a->name() < b->name(); }); for (auto page : configPages) { cfgDlg->appendConfigPage(page); } QObject::connect(cfgDlg, &ConfigDialog::configSaved, cfgDlg, [this, proj](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(proj, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(proj); }); - cfgDlg->setWindowTitle(i18n("Configure Project %1", proj->name())); + cfgDlg->setWindowTitle(i18nc("@title:window", "Configure Project %1", proj->name())); QObject::connect(cfgDlg, &KDevelop::ConfigDialog::finished, proj, [proj]() { proj->projectConfiguration()->sync(); }); cfgDlg->show(); } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); for (IProject* project : qAsConst(m_projects)) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { const QList auxBuilders = topBuilder->additionalBuilderPlugins(project); destination.append( auxBuilders ); for (IProjectBuilder* auxBuilder : auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) const { const QList plugins = m_core->pluginController()->loadedPlugins(); const IBuildSystemManager* const buildSystemManager = project->buildSystemManager(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if (buildSystemManager) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } for (auto plugin : plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); auto* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } auto* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } // Do not show config pages for analyzer tools which need a buildSystemManager // TODO: turn into generic feature to disable plugin config pages which do not apply for a project if (!buildSystemManager) { const auto required = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-IRequired")); if (required.contains(QLatin1String("org.kdevelop.IBuildSystemManager"))) { continue; } } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates() { // if only one project loaded, this is always our target int itemCount = (m_projects.size() == 1) ? 1 : 0; if (itemCount == 0) { // otherwise base on selection auto* itemContext = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (itemContext) { itemCount = itemContext->items().count(); } } m_openConfig->setEnabled(itemCount == 1); m_closeProject->setEnabled(itemCount > 0); } QSet selectedProjects() { QSet projects; // if only one project loaded, this is our target if (m_projects.count() == 1) { projects.insert(m_projects.at(0)); } else { // otherwise base on selection auto* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx) { const auto items = ctx->items(); for (ProjectBaseItem* item : items) { projects.insert(item->project()); } } } return projects; } void openProjectConfig() { auto projects = selectedProjects(); if (projects.count() == 1) { q->configureProject(*projects.constBegin()); } } void closeSelectedProjects() { const auto projects = selectedProjects(); for (IProject* project : projects) { q->closeProject(project); } } void importProject(const QUrl& url_) { QUrl url(url_); if (url.isLocalFile()) { const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!path.isEmpty()) { url = QUrl::fromLocalFile(path); } } if ( !url.isValid() ) { const QString messageText = i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile)); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; const QString messageText = i18n("Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile)); auto* message = new Sublime::Message(messageText, Sublime::Message::Information); message->setAutoHide(0); ICore::self()->uiController()->postMessage(message); return; } const auto projects = m_projects; for (IProject* project : projects) { if ( url == project->projectFile().toUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); auto* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code")); ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code")); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "CreatedFrom", createdFrom ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } // Here and above we take .filename() part of the selectedUrl() to make it relative to the project root, // thus keeping .kdev file relocatable return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo::exists( u.toLocalFile() ); } else { #if KIO_VERSION >= QT_VERSION_CHECK(5, 69, 0) auto statJob = KIO::statDetails(u, KIO::StatJob::DestinationSide, KIO::StatNoDetails, KIO::HideProgressInfo); #else auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); #endif KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl, const QUrl& repoUrl, IPlugin* vcsOrProviderPlugin) { Q_ASSERT(d); ScopedDialog dlg(fetch, startUrl, repoUrl, vcsOrProviderPlugin, Core::self()->uiController()->activeMainWindow()); if(dlg->exec() == QDialog::Rejected) { return QUrl(); } QUrl projectFileUrl = dlg->projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg->projectName() << dlg->projectManager(); if ( dlg->projectManager() == QLatin1String("") ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl == dlg->selectedUrl() ) { if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), dlg); } } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); - yes.setText(i18n("Override")); - yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); + yes.setText(i18nc("@action:button", "Override")); + yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); - no.setText(i18n("Open Existing File")); - no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); + no.setText(i18nc("@action:button", "Open Existing File")); + no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); - cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); + cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), - i18n("Override existing project configuration"), yes, no, cancel ); + i18nc("@title:window", "Override Existing Project Configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { Path projectConfigDir(projectFileUrl); projectConfigDir.setLastPathSegment(QStringLiteral(".kdev4")); auto delJob = KIO::del(projectConfigDir.toUrl()); delJob->exec(); if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg)) { const QString messageText = i18n("Unable to create configuration file %1", projectFileUrl.url()); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_D(ProjectController); Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController(core) , d_ptr(new ProjectControllerPrivate(core, this)) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { Q_D(ProjectController); KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( QStringLiteral("project_open") ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows one to select a KDevelop4 project file " "or an existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") ); connect(action, &QAction::triggered, this, [this] { Q_D(ProjectController); d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") ); connect(action, &QAction::triggered, this, [this] { Q_D(ProjectController); d->openProjectConfig(); } ); - action->setText( i18n( "Open Configuration..." ) ); + action->setText( i18nc("@action", "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) ); action->setEnabled( false ); action = ac->addAction( QStringLiteral("commit_current_project") ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); - action->setText( i18n( "Commit Current Project..." ) ); - action->setIconText( i18n( "Commit..." ) ); + action->setText( i18nc("@action", "Commit Current Project..." ) ); + action->setIconText( i18nc("@action", "Commit..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [this] (Sublime::Area* area) { Q_D(ProjectController); d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentProjectsAction ); - d->m_recentProjectsAction->setText( i18n( "Open Recent Project" ) ); + d->m_recentProjectsAction->setText( i18nc("@action", "Open Recent Project" ) ); d->m_recentProjectsAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentProjectsAction->loadEntries( KConfigGroup(config, "RecentProjects") ); auto* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); - openProjectForFileAction->setText(i18n("Open Project for Current File")); + openProjectForFileAction->setText(i18nc("@action", "Open Project for Current File")); openProjectForFileAction->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { Q_D(ProjectController); delete d->model; delete d->dialog; } void ProjectController::cleanup() { Q_D(ProjectController); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } saveRecentProjectsActionEntries(); d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } closeAllProjects(); } void ProjectController::saveRecentProjectsActionEntries() { Q_D(ProjectController); if (!d->m_recentProjectsAction) return; auto config = KSharedConfig::openConfig(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentProjectsAction->saveEntries( recentGroup ); config->sync(); } void ProjectController::initialize() { Q_D(ProjectController); d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); d->m_changesModel = new ProjectChangesModel(this); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"), this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); const auto projects = group.readEntry( "Open Projects", QList() ); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [this]() { Q_D(ProjectController); d->updateActionStates(); } ); connect(this, &ProjectController::projectOpened, this, [this]() { Q_D(ProjectController); d->updateActionStates(); }); connect(this, &ProjectController::projectClosing, this, [this]() { Q_D(ProjectController); d->updateActionStates(); }); QTimer::singleShot(0, this, [this, projects](){ openProjects(projects); emit initialized(); }); } void ProjectController::openProjects(const QList& projects) { for (const QUrl& url : projects) { openProject(url); } } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { Q_D(const ProjectController); return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { Q_D(const ProjectController); if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return nullptr; } QList ProjectController::projects() const { Q_D(const ProjectController); return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, const KIO::UDSEntryList& entries) { Q_D(ProjectController); auto* job = qobject_cast(_job); Q_ASSERT(job); for (const KIO::UDSEntry& entry : entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(QLatin1String(".kdev4"))) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ auto* message = new Sublime::Message(i18n("Project already open: %1", project->name()), Sublime::Message::Error); Core::self()->uiController()->postMessage(message); } }else{ auto* message = new Sublime::Message(i18n("No active document"), Sublime::Message::Error); Core::self()->uiController()->postMessage(message); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_D(ProjectController); Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl; if (sourceUrl.isLocalFile() && !QFileInfo(sourceUrl.toLocalFile()).isDir()) { dirUrl = dirUrl.adjusted(QUrl::RemoveFilename); } QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { Q_D(ProjectController); QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { const auto sessions = Core::self()->sessionController()->sessions(); for (const Session* session : sessions) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees const auto windows = Core::self()->uiController()->controller()->mainWindows(); for (Sublime::MainWindow* window : windows) { window->close(); } } #endif } } } if ( ! existingSessions.isEmpty() ) { ScopedDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); - dialog->setWindowTitle(i18n("Project Already Open")); + dialog->setWindowTitle(i18nc("@title:window", "Project Already Open")); auto mainLayout = new QVBoxLayout(dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); - auto* newSession = new QRadioButton(i18n("Add project to current session")); + auto* newSession = new QRadioButton(i18nc("@option:radio", "Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); for (const Session* session : qAsConst(existingSessions)) { - auto* button = new QRadioButton(i18n("Open session %1", session->description())); + auto* button = new QRadioButton(i18nc("@option:radio", "Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if (!dialog->exec()) return; for (const QObject* obj : sessions.children()) { if ( const auto* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } bool ProjectController::fetchProjectFromUrl(const QUrl& repoUrl, FetchFlags fetchFlags) { Q_D(ProjectController); IPlugin* vcsOrProviderPlugin = nullptr; // TODO: query also projectprovider plugins, and that before plain vcs plugins // e.g. KDE provider plugin could catch URLs from mirror or pickup kde:repo things auto* pluginController = d->m_core->pluginController(); const auto& vcsPlugins = pluginController->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); for (auto* plugin : vcsPlugins) { auto* iface = plugin->extension(); if (iface->isValidRemoteRepositoryUrl(repoUrl)) { vcsOrProviderPlugin = plugin; break; } } if (!vcsOrProviderPlugin) { if (fetchFlags.testFlag(FetchShowErrorIfNotSupported)) { const QString messageText = i18n("No enabled plugin supports this repository URL: %1", repoUrl.toDisplayString()); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); } return false; } const QUrl url = d->dialog->askProjectConfigLocation(true, QUrl(), repoUrl, vcsOrProviderPlugin); if (!url.isEmpty()) { d->importProject(url); } return true; } void ProjectController::fetchProject() { Q_D(ProjectController); QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { Q_D(ProjectController); if( !project ) { qCWarning(SHELL) << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentProjectsAction->addUrl( project->projectFile().toUrl() ); saveRecentProjectsActionEntries(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl())); d->m_currentlyOpening.removeAll(project->projectFile().toUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { Q_D(ProjectController); const QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; for (const QList& _list : qAsConst(d->m_projectPlugins)) { otherProjectPlugins << _list; } #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QSet pluginsForProjSet(pluginsForProj.begin(), pluginsForProj.end()); QSet otherPrjPluginsSet(otherProjectPlugins.constBegin(), otherProjectPlugins.constEnd()); #else QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); #endif // loaded - target = tobe unloaded. const QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); for (IPlugin* _plugin : tobeRemoved) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { const auto documents = Core::self()->documentController()->openDocuments(); for (IDocument* doc : documents) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [this] { Q_D(ProjectController); d->unloadAllProjectPlugins(); }); } void ProjectController::takeProject(IProject* proj) { Q_D(ProjectController); if (!proj) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); } void ProjectController::closeProject(IProject* proj) { takeProject(proj); proj->deleteLater(); // be safe when deleting } void ProjectController::closeAllProjects() { Q_D(ProjectController); const auto projects = d->m_projects; for (auto* project : projects) { closeProject(project); } } void ProjectController::abortOpeningProject(IProject* proj) { Q_D(ProjectController); d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { Q_D(ProjectController); return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { Q_D(const ProjectController); if (d->m_projects.isEmpty()) { return nullptr; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return nullptr; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_D(ProjectController); auto it = std::find_if(d->m_projects.constBegin(), d->m_projects.constEnd(), [&](IProject* proj) { return (proj->name() == name); }); return (it != d->m_projects.constEnd()) ? *it : nullptr; } void ProjectController::configureProject( IProject* project ) { Q_D(ProjectController); d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { Q_D(ProjectController); Q_ASSERT(project); if (d->m_projects.contains(project)) { qCWarning(SHELL) << "Project already tracked by this project controller:" << project; return; } // fake-emit signals so listeners are aware of a new project being added emit projectAboutToBeOpened(project); project->setParent(this); d->m_projects.append(project); emit projectOpened(project); } bool ProjectController::isProjectNameUsed( const QString& name ) const { const auto projects = this->projects(); return std::any_of(projects.begin(), projects.end(), [&](IProject* p) { return (p->name() == name); }); } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry("Projects Base Directory", QUrl::fromLocalFile(QDir::homePath() + QLatin1String("/projects"))); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least const auto projects = Core::self()->projectController()->projects(); auto it = std::find_if(projects.begin(), projects.end(), [&](IProject* candidateProject) { return (candidateProject->path().toUrl().isParentOf(url)); }); if (it != projects.end()) { project = *it; } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = QLatin1String("") + project->name() + QLatin1String("/"); } else { prefixText = project->name() + QLatin1Char(':'); } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath.remove(0, 2); } if (!relativePath.isEmpty()) { prefixText += relativePath + QLatin1Char('/'); } } else { prefixText = parent.pathOrUrl() + QLatin1Char('/'); } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return QLatin1String("") + project->name() + QLatin1String(""); } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + QLatin1String("") + url.fileName() + QLatin1String(""); } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension(Context* ctx, QWidget* parent) { Q_D(ProjectController); Q_UNUSED(parent); ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext) { return ext; } if (!static_cast(ctx)->items().isEmpty() ) { - auto* action = new QAction(i18n("Reparse the Entire Project"), this); + auto* action = new QAction(i18nc("@action", "Reparse the Entire Project"), this); connect(action, &QAction::triggered, this, [this] { Q_D(ProjectController); const auto projects = d->selectedProjects(); for (auto* project : projects) { reparseProject(project, true, true); } }); ext.addAction(ContextMenuExtension::ProjectGroup, action); return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentProjectsAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { Q_D(ProjectController); return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { Q_D(ProjectController); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); auto* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); auto* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { ScopedDialog commitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Q_D(const ProjectController); Path path(path_); IProject* sourceDirProject = nullptr, *buildDirProject = nullptr; for (IProject* proj : qAsConst(d->m_projects)) { if(proj->path().isParentOf(path) || proj->path() == path) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void KDevelop::ProjectController::reparseProject(IProject *project, bool forceUpdate, bool forceAll) { Q_D(ProjectController); if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate, forceAll); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } diff --git a/kdevplatform/shell/projectinfopage.ui b/kdevplatform/shell/projectinfopage.ui index ce3f4dc3bf..045663b791 100644 --- a/kdevplatform/shell/projectinfopage.ui +++ b/kdevplatform/shell/projectinfopage.ui @@ -1,64 +1,64 @@ ProjectInfoPage 0 0 651 416 0 0 0 0 - Select the Management Plugin to use for this project + Select the Management Plugin to use for this project - Project manager: + Project manager: - Provide a name for the project + Provide a name for the project - Name: + Name: - Provide a name for the project + Provide a name for the project - Select the Management Plugin to use for this project + Select the Management Plugin to use for this project diff --git a/kdevplatform/shell/projectsourcepage.cpp b/kdevplatform/shell/projectsourcepage.cpp index a920e498d9..3b8d863012 100644 --- a/kdevplatform/shell/projectsourcepage.cpp +++ b/kdevplatform/shell/projectsourcepage.cpp @@ -1,301 +1,301 @@ /*************************************************************************** * Copyright (C) 2010 by Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "projectsourcepage.h" #include "ui_projectsourcepage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static const int FROM_FILESYSTEM_SOURCE_INDEX = 0; ProjectSourcePage::ProjectSourcePage(const QUrl& initial, const QUrl& repoUrl, IPlugin* preSelectPlugin, QWidget* parent) : QWidget(parent) { m_ui = new Ui::ProjectSourcePage; m_ui->setupUi(this); m_ui->status->setCloseButtonVisible(false); m_ui->status->setMessageType(KMessageWidget::Error); m_ui->workingDir->setUrl(initial); m_ui->workingDir->setMode(KFile::Directory); - m_ui->sources->addItem(QIcon::fromTheme(QStringLiteral("folder")), i18n("From File System")); + m_ui->sources->addItem(QIcon::fromTheme(QStringLiteral("folder")), i18nc("@item:inlistbox", "From File System")); m_plugins.append(nullptr); int preselectIndex = -1; IPluginController* pluginManager = ICore::self()->pluginController(); const QList vcsPlugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IBasicVersionControl") ); m_plugins.reserve(m_plugins.size() + vcsPlugins.size()); for (IPlugin* p : vcsPlugins) { if (p == preSelectPlugin) { preselectIndex = m_plugins.count(); } m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } const QList projectPlugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IProjectProvider") ); m_plugins.reserve(m_plugins.size() + projectPlugins.size()); for (IPlugin* p : projectPlugins) { if (p == preSelectPlugin) { preselectIndex = m_plugins.count(); } m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } if (preselectIndex == -1) { // "From File System" is quite unlikely to be what the user wants, so default to first real plugin... const int defaultIndex = (m_plugins.count() > 1) ? 1 : 0; KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers"); preselectIndex = configGroup.readEntry("LastProviderIndex", defaultIndex); } preselectIndex = qBound(0, preselectIndex, m_ui->sources->count() - 1); m_ui->sources->setCurrentIndex(preselectIndex); setSourceWidget(preselectIndex, repoUrl); // connect as last step, otherwise KMessageWidget could get both animatedHide() and animatedShow() // during setup and due to a bug will ignore any but the first call // Only fixed for KF5 5.32 connect(m_ui->workingDir, &KUrlRequester::textChanged, this, &ProjectSourcePage::reevaluateCorrection); connect(m_ui->sources, QOverload::of(&QComboBox::currentIndexChanged), this, &ProjectSourcePage::setSourceIndex); connect(m_ui->get, &QPushButton::clicked, this, &ProjectSourcePage::checkoutVcsProject); } ProjectSourcePage::~ProjectSourcePage() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers"); configGroup.writeEntry("LastProviderIndex", m_ui->sources->currentIndex()); delete m_ui; } void ProjectSourcePage::setSourceIndex(int index) { setSourceWidget(index, QUrl()); } void ProjectSourcePage::setSourceWidget(int index, const QUrl& repoUrl) { m_locationWidget = nullptr; m_providerWidget = nullptr; QLayoutItem *child; while ((child = m_ui->remoteWidgetLayout->takeAt(0)) != nullptr) { delete child->widget(); delete child; } IBasicVersionControl* vcIface = vcsPerIndex(index); IProjectProvider* providerIface; bool found=false; if(vcIface) { found=true; m_locationWidget=vcIface->vcsLocation(m_ui->sourceBox); connect(m_locationWidget, &VcsLocationWidget::changed, this, &ProjectSourcePage::locationChanged); // set after connect, to trigger handler if (!repoUrl.isEmpty()) { m_locationWidget->setLocation(repoUrl); } m_ui->remoteWidgetLayout->addWidget(m_locationWidget); } else { providerIface = providerPerIndex(index); if(providerIface) { found=true; m_providerWidget=providerIface->providerWidget(m_ui->sourceBox); connect(m_providerWidget, &IProjectProviderWidget::changed, this, &ProjectSourcePage::projectChanged); m_ui->remoteWidgetLayout->addWidget(m_providerWidget); } } reevaluateCorrection(); m_ui->sourceBox->setVisible(found); } IBasicVersionControl* ProjectSourcePage::vcsPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return nullptr; else return p->extension(); } IProjectProvider* ProjectSourcePage::providerPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return nullptr; else return p->extension(); } VcsJob* ProjectSourcePage::jobPerCurrent() { QUrl url=m_ui->workingDir->url(); IPlugin* p=m_plugins[m_ui->sources->currentIndex()]; VcsJob* job=nullptr; if(auto* iface=p->extension()) { Q_ASSERT(iface && m_locationWidget); job=iface->createWorkingCopy(m_locationWidget->location(), url); } else if(m_providerWidget) { job=m_providerWidget->createWorkingCopy(url); } return job; } void ProjectSourcePage::checkoutVcsProject() { QUrl url=m_ui->workingDir->url(); QDir d(url.toLocalFile()); if(!url.isLocalFile() && !d.exists()) { bool corr = d.mkpath(d.path()); if(!corr) { KMessageBox::error(nullptr, i18n("Could not create the directory: %1", d.path())); return; } } VcsJob* job=jobPerCurrent(); if (!job) { return; } m_ui->sources->setEnabled(false); m_ui->sourceBox->setEnabled(false); m_ui->workingDir->setEnabled(false); m_ui->get->setEnabled(false); m_ui->creationProgress->setValue(m_ui->creationProgress->minimum()); connect(job, &VcsJob::result, this, &ProjectSourcePage::projectReceived); // Can't use new signal-slot syntax, KJob::percent is private :/ connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(progressChanged(KJob*,ulong))); connect(job, &VcsJob::infoMessage, this, &ProjectSourcePage::infoMessage); ICore::self()->runController()->registerJob(job); } void ProjectSourcePage::progressChanged(KJob*, unsigned long value) { m_ui->creationProgress->setValue(value); } void ProjectSourcePage::infoMessage(KJob* , const QString& text, const QString& /*rich*/) { m_ui->creationProgress->setFormat(i18nc("Format of the progress bar text. progress and info", "%1 : %p%", text)); } void ProjectSourcePage::projectReceived(KJob* job) { if (job->error()) { m_ui->creationProgress->setValue(0); } else { m_ui->creationProgress->setValue(m_ui->creationProgress->maximum()); } reevaluateCorrection(); m_ui->creationProgress->setFormat(QStringLiteral("%p%")); } void ProjectSourcePage::reevaluateCorrection() { //TODO: Probably we should just ignore remote URL's, I don't think we're ever going //to support checking out to remote directories const QUrl cwd = m_ui->workingDir->url(); const QDir dir = cwd.toLocalFile(); // case where we import a project from local file system if (m_ui->sources->currentIndex() == FROM_FILESYSTEM_SOURCE_INDEX) { emit isCorrect(dir.exists()); return; } // all other cases where remote locations need to be specified bool correct=!cwd.isRelative() && (!cwd.isLocalFile() || QDir(cwd.adjusted(QUrl::RemoveFilename).toLocalFile()).exists()); emit isCorrect(correct && m_ui->creationProgress->value() == m_ui->creationProgress->maximum()); const bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) || (m_providerWidget && m_providerWidget->isCorrect())); const bool isFolderEmpty = (correct && cwd.isLocalFile() && dir.exists() && dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()); const bool validToCheckout = correct && validWidget && (!dir.exists() || isFolderEmpty); m_ui->get->setEnabled(validToCheckout); m_ui->creationProgress->setEnabled(validToCheckout); if(!correct) setStatus(i18n("You need to specify a valid or nonexistent directory to check out a project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled() && !validWidget) setStatus(i18n("You need to specify the source for your remote project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled() && !isFolderEmpty) setStatus(i18n("You need to specify an empty folder as your project destination")); else clearStatus(); } void ProjectSourcePage::locationChanged() { Q_ASSERT(m_locationWidget); if(m_locationWidget->isCorrect()) { QString currentUrl = m_ui->workingDir->text(); currentUrl.truncate(currentUrl.lastIndexOf(QLatin1Char('/'))+1); QUrl current = QUrl::fromUserInput(currentUrl + m_locationWidget->projectName()); m_ui->workingDir->setUrl(current); } else reevaluateCorrection(); } void ProjectSourcePage::projectChanged(const QString& name) { Q_ASSERT(m_providerWidget); QString currentUrl = m_ui->workingDir->text(); currentUrl.truncate(currentUrl.lastIndexOf(QLatin1Char('/'))+1); QUrl current = QUrl::fromUserInput(currentUrl + name); m_ui->workingDir->setUrl(current); } void ProjectSourcePage::setStatus(const QString& message) { m_ui->status->setText(message); m_ui->status->animatedShow(); } void ProjectSourcePage::clearStatus() { m_ui->status->animatedHide(); } QUrl ProjectSourcePage::workingDir() const { return m_ui->workingDir->url(); } diff --git a/kdevplatform/shell/projectsourcepage.ui b/kdevplatform/shell/projectsourcepage.ui index f537333a9d..e38c1c2054 100644 --- a/kdevplatform/shell/projectsourcepage.ui +++ b/kdevplatform/shell/projectsourcepage.ui @@ -1,158 +1,158 @@ ProjectSourcePage 0 0 420 353 0 0 0 0 0 0 - Source: + Source: - Destination + Destination - Directory: + Directory: - Select the directory to use... + Select the directory to use... 0 0 - Source + Source 0 0 0 0 false - Get + Get false 0 Qt::Vertical 20 40 KUrlRequester QWidget
KUrlRequester
1
KMessageWidget QFrame
KMessageWidget
1
diff --git a/kdevplatform/shell/runcontroller.cpp b/kdevplatform/shell/runcontroller.cpp index ae051ce1e4..5f11b924d2 100644 --- a/kdevplatform/shell/runcontroller.cpp +++ b/kdevplatform/shell/runcontroller.cpp @@ -1,1093 +1,1093 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "unitylauncher.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } using Target = QPair; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); } QString id() const override { return QStringLiteral("debug"); } - QString name() const override { return i18n("Debug"); } + QString name() const override { return i18nc("launch mode", "Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); } QString id() const override { return QStringLiteral("profile"); } - QString name() const override { return i18n("Profile"); } + QString name() const override { return i18nc("launch mode", "Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); } QString id() const override { return QStringLiteral("execute"); } - QString name() const override { return i18n("Execute"); } + QString name() const override { return i18nc("launch mode", "Execute"); } }; class KDevelop::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; UnityLauncher* unityLauncher; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { const auto launchers = type->launchers(); auto it = std::find_if(launchers.begin(), launchers.end(), [&](ILauncher* l) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); return (l->supportedModes().contains(mode->id())); }); if (it != launchers.end()) { ILauncher* launcher = *it; QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); auto it = std::find_if(launchConfigurations.constBegin(), launchConfigurations.constEnd(), [&] (LaunchConfiguration* l) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; return true; } return false; }); ILaunchConfiguration* ilaunch = (it != launchConfigurations.constEnd()) ? *it : nullptr; if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); auto* launch = static_cast(ilaunch); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = nullptr; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { const auto actions = currentTargetAction->actions(); for (QAction* a : actions) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().at(0)->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(QVariant::fromValue(l)); } void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList()); for (const QString& cfg : configs) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return nullptr; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d_ptr(new RunControllerPrivate) { Q_D(RunController); setObjectName(QStringLiteral("RunController")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"), this, QDBusConnection::ExportScriptableSlots); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = nullptr; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->contextItem = nullptr; d->executeMode = nullptr; d->debugMode = nullptr; d->profileMode = nullptr; d->unityLauncher = new UnityLauncher(this); d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName()); if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() = default; void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { Q_D(RunController); delete d->executeMode; d->executeMode = nullptr; delete d->profileMode; d->profileMode = nullptr; delete d->debugMode; d->debugMode = nullptr; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { Q_D(RunController); d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr ); const auto projects = Core::self()->projectController()->projects(); for (IProject* project : projects) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return nullptr; } auto* run = static_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { const QString messageText = i18n("The current launch configuration does not support the '%1' mode.", runMode); auto* message = new Sublime::Message(messageText, Sublime::Message::Error); ICore::self()->uiController()->postMessage(message); return nullptr; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { Q_D(RunController); QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); - action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Launches..."), this); + action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action", "Configure Launches..."), this); ac->addAction(QStringLiteral("configure_launches"), action); action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog); - d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Execute Launch"), this); - d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); + d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18nc("@action", "Execute Launch"), this); + d->runAction->setIconText( i18nc("@action Short text for 'Execute Launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction(QStringLiteral("run_execute"), d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); - d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18n("Debug Launch"), this); + d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18nc("@action", "Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::ALT + Qt::Key_F9); - d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); + d->dbgAction->setIconText( i18nc("@action Short text for 'Debug Launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction(QStringLiteral("run_debug"), d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); - action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop All Jobs"), this); - action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); + action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop All Jobs"), this); + action->setIconText(i18nc("@action Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape"))); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_all"), action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action); - action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Stop"), this); - action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); + action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop"), this); + action->setIconText(i18nc("@action Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction(QStringLiteral("run_stop_menu"), action); - d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); + d->currentTargetAction = new KSelectAction( i18nc("@title:menu", "Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { Q_D(RunController); return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { Q_D(RunController); d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { Q_D(RunController); if (!d->currentTargetAction) return; const auto actions = d->currentTargetAction->actions(); for (QAction* action : actions) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().at(0)->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("debug") ); } } void RunController::slotProfile() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("profile") ); } } void RunController::slotExecute() { Q_D(RunController); if (d->launchConfigurations.isEmpty()) { showConfigurationDialog(); } if (!d->launchConfigurations.isEmpty()) { executeDefaultLaunch( QStringLiteral("execute") ); } } void KDevelop::RunController::showConfigurationDialog() const { LaunchConfigurationDialog dlg; dlg.exec(); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { Q_D(const RunController); QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return nullptr; } void KDevelop::RunController::registerJob(KJob * job) { Q_D(RunController); if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = nullptr; if (Core::self()->setupFlags() != Core::NoUi) { - stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this); + stopJobAction = new QAction(job->objectName().isEmpty() ? i18nc("@item:inmenu", "<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); // FIXME percent is a private signal and thus we cannot use new connect syntax connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(jobPercentChanged())); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { Q_D(RunController); IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { Q_D(RunController); bool running = false; int jobCount = 0; int totalProgress = 0; for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) { KJob *job = it.key(); if (!job->isSuspended()) { running = true; ++jobCount; totalProgress += job->percent(); } } d->unityLauncher->setProgressVisible(running); if (jobCount > 0) { d->unityLauncher->setProgress((totalProgress + 1) / jobCount); } else { d->unityLauncher->setProgress(0); } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { Q_D(RunController); // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 const auto jobs = d->jobs.keys(); for (KJob* job : jobs) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qCWarning(SHELL) << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { auto* action = qobject_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { auto* message = new Sublime::Message(job->errorString(), Sublime::Message::Error); Core::self()->uiController()->postMessage(message); } } } void RunController::jobDestroyed(QObject* job) { Q_D(RunController); KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qCWarning(SHELL) << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void RunController::jobPercentChanged() { checkState(); } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { Q_D(const RunController); return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; const auto configsInternal = launchConfigurationsInternal(); configs.reserve(configsInternal.size()); for (LaunchConfiguration* config : configsInternal) { configs << config; } return configs; } QList RunController::launchConfigurationsInternal() const { Q_D(const RunController); return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { Q_D(const RunController); return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { Q_D(RunController); const auto oldLaunchConfigurations = d->launchConfigurations; for (LaunchConfiguration* l : oldLaunchConfigurations) { if( l->type() == type ) { removeLaunchConfigurationInternal( l ); } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { Q_D(const RunController); return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { Q_D(RunController); d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { Q_D(const RunController); auto it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return nullptr; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { Q_D(RunController); if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); removeLaunchConfigurationInternal( l ); } void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().at(0)->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if (auto dl = defaultLaunch()) { execute(runMode, dl); } else { qCWarning(SHELL) << "no default launch!"; } } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { Q_D(RunController); const auto actions = d->currentTargetAction->actions(); for (QAction* a : actions) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { const auto configs = Core::self()->runControllerInternal()->launchConfigurations(); return std::any_of(configs.begin(), configs.end(), [&](ILaunchConfiguration* config) { return (config->name() == name); }); } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = QStringLiteral("Launch Configuration"); while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); auto* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { Q_D(const RunController); return d->delegate; } ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent) { Q_D(RunController); d->launchAsInfo.clear(); d->contextItem = nullptr; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { auto* prjctx = static_cast(ctx); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; for (ILaunchMode* mode : qAsConst(d->launchModes)) { - auto* menu = new KActionMenu(i18n("%1 As...", mode->name() ), parent); + auto* menu = new KActionMenu(i18nc("@title:menu", "%1 As...", mode->name() ), parent); const auto types = launchConfigurationTypes(); for (LaunchConfigurationType* type : types) { bool hasLauncher = false; const auto launchers = type->launchers(); for (ILauncher* launcher : launchers) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); auto* act = new QAction(menu); act->setText( type->name() ); qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name(); connect(act, &QAction::triggered, this, [this, i] () { Q_D(RunController); d->launchAs(i); } ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } else { delete menu; } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/kdevplatform/shell/savedialog.cpp b/kdevplatform/shell/savedialog.cpp index 3f5d2681ad..91693abee5 100644 --- a/kdevplatform/shell/savedialog.cpp +++ b/kdevplatform/shell/savedialog.cpp @@ -1,98 +1,98 @@ /* This file is part of the KDE project Copyright (C) 2002 Harald Fernengel Copyright (C) 2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "savedialog.h" #include #include #include #include #include #include #include #include using namespace KDevelop; class DocumentItem : public QListWidgetItem { public: DocumentItem( IDocument* doc, QListWidget* parent ) : QListWidgetItem(parent) , m_doc( doc ) { setFlags(Qt::ItemIsUserCheckable | flags()); setData(Qt::CheckStateRole, Qt::Checked); setText(m_doc->url().toDisplayString(QUrl::PreferLocalFile)); } IDocument* doc() const { return m_doc; } private: IDocument* const m_doc; }; KSaveSelectDialog::KSaveSelectDialog( const QList& files, QWidget * parent ) : QDialog( parent ) { - setWindowTitle( i18n("Save Modified Files?") ); + setWindowTitle( i18nc("@title:window", "Save Modified Files?") ); auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(new QLabel( i18n("The following files have been modified. Save them?"), this )); m_listWidget = new QListWidget(this); mainLayout->addWidget(m_listWidget); // m_listWidget->addColumn( "" ); // m_listWidget->header()->hide(); // m_listWidget->setSectionResizeMode( QListView::LastColumn ); for (IDocument* doc : files) { new DocumentItem( doc, m_listWidget ); } auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Save|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Save); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &KSaveSelectDialog::save); connect(buttonBox, &QDialogButtonBox::rejected, this, &KSaveSelectDialog::reject); - auto user1Button = buttonBox->addButton(i18n("Save &None" ), QDialogButtonBox::ActionRole); - user1Button->setToolTip(i18n("Discard all modifications" )); + auto user1Button = buttonBox->addButton(i18nc("@action:button", "Save &None" ), QDialogButtonBox::ActionRole); + user1Button->setToolTip(i18nc("@info:tooltip", "Discard all modifications" )); connect(user1Button, &QPushButton::clicked, this, &KSaveSelectDialog::accept); mainLayout->addWidget(buttonBox); } KSaveSelectDialog::~KSaveSelectDialog() { } void KSaveSelectDialog::save( ) { for (int i = 0; i < m_listWidget->count(); ++i) { auto* item = static_cast(m_listWidget->item(i)); if (item->data(Qt::CheckStateRole).toBool()) item->doc()->save(IDocument::Silent); } accept(); } diff --git a/kdevplatform/shell/sessionchooserdialog.cpp b/kdevplatform/shell/sessionchooserdialog.cpp index 8a0ffe5f82..f5b5ef3bc5 100644 --- a/kdevplatform/shell/sessionchooserdialog.cpp +++ b/kdevplatform/shell/sessionchooserdialog.cpp @@ -1,230 +1,230 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessionchooserdialog.h" #include "sessioncontroller.h" #include "core.h" #include #include #include #include #include #include #include #include using namespace KDevelop; SessionChooserDialog::SessionChooserDialog(QListView* view, QAbstractItemModel* model, QLineEdit* filter) : m_view(view), m_model(model), m_filter(filter), m_deleteCandidateRow(-1) { m_updateStateTimer.setInterval(5000); m_updateStateTimer.setSingleShot(false); m_updateStateTimer.start(); connect(&m_updateStateTimer, &QTimer::timeout, this, &SessionChooserDialog::updateState); connect(view, &QListView::doubleClicked, this, &SessionChooserDialog::doubleClicked); connect(view, &QListView::entered, this, &SessionChooserDialog::itemEntered); m_deleteButton = new QPushButton(view->viewport()); m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); - m_deleteButton->setToolTip(i18nc("@info", "Delete session")); + m_deleteButton->setToolTip(i18nc("@info:tooltip", "Delete session")); m_deleteButton->hide(); connect(m_deleteButton, &QPushButton::clicked, this, &SessionChooserDialog::deleteButtonPressed); m_deleteButtonTimer.setInterval(500); m_deleteButtonTimer.setSingleShot(true); connect(&m_deleteButtonTimer, &QTimer::timeout, this, &SessionChooserDialog::showDeleteButton); view->setMouseTracking(true); view->installEventFilter(this); filter->installEventFilter(this); connect(filter, &QLineEdit::textChanged, this, &SessionChooserDialog::filterTextChanged); - setWindowTitle(i18n("Pick a Session")); + setWindowTitle(i18nc("@title:window", "Pick a Session")); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); auto mainLayout = new QVBoxLayout(this); m_mainWidget = new QWidget(this); mainLayout->addWidget(m_mainWidget); QPushButton *okButton = m_buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::Key_Return); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SessionChooserDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &SessionChooserDialog::reject); mainLayout->addWidget(m_buttonBox); - okButton->setText(i18n("Run")); + okButton->setText(i18nc("@action:button", "Run")); okButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); } void SessionChooserDialog::filterTextChanged() { m_view->selectionModel()->setCurrentIndex(m_model->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); bool enabled = m_view->model()->rowCount(QModelIndex())>0; m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); m_deleteButton->setVisible(false); } void SessionChooserDialog::doubleClicked(const QModelIndex& index) { if(m_model->flags(index) & Qt::ItemIsEnabled) accept(); } void SessionChooserDialog::updateState() { // Sometimes locking may take some time, so we stop the timer, to prevent an 'avalanche' of events m_updateStateTimer.stop(); for(int row = 0; row < m_model->rowCount(); ++row) { QString session = m_model->index(row, 0).data().toString(); if(session.isEmpty()) //create new session continue; QString state, tooltip; SessionRunInfo info = SessionController::sessionRunInfo(session); if(info.isRunning) { - tooltip = i18n("Active session.\npid %1, app %2, host %3", + tooltip = i18nc("@info:tooltip", "Active session.\npid %1, app %2, host %3", info.holderPid, info.holderApp, info.holderHostname); state = i18n("Running"); } m_model->setData(m_model->index(row, 1), !info.isRunning ? QIcon() : QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::DecorationRole); m_model->setData(m_model->index(row, 1), tooltip, Qt::ToolTipRole); m_model->setData(m_model->index(row, 2), state, Qt::DisplayRole); } m_updateStateTimer.start(); } void SessionChooserDialog::itemEntered(const QModelIndex& index) { // The last row says "Create new session", we don't want to delete that if(index.row() == m_model->rowCount()-1) { m_deleteButton->hide(); m_deleteButtonTimer.stop(); return; } // align the delete-button to stay on the right border of the item // we need the right most column's index QModelIndex in = m_model->index( index.row(), 1 ); const QRect rect = m_view->visualRect(in); m_deleteButton->resize(rect.height(), rect.height()); QPoint p(rect.right() - m_deleteButton->size().width(), rect.top()+rect.height()/2-m_deleteButton->height()/2); m_deleteButton->move(p); m_deleteCandidateRow = index.row(); m_deleteButtonTimer.start(); } void SessionChooserDialog::showDeleteButton() { m_deleteButton->show(); } bool SessionChooserDialog::eventFilter(QObject* object, QEvent* event) { if(object == m_view && event->type() == QEvent::Leave ) { m_deleteButtonTimer.stop(); m_deleteButton->hide(); // don't eat the event, pass on } else if (object == m_filter && event->type() == QEvent::KeyPress) { auto *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Return) { accept(); // don't eat the event, pass on } else if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { QModelIndex currentIndex = m_view->selectionModel()->currentIndex(); int selectRow = -1; if (keyEvent->key() == Qt::Key_Up) { if(!currentIndex.isValid()) { selectRow = m_model->rowCount()-1; } else if(currentIndex.row()-1 >= 0) { selectRow = currentIndex.row()-1; } } else { if(!currentIndex.isValid()) { selectRow = 0; } else if(currentIndex.row()+1 < m_model->rowCount()) { selectRow = currentIndex.row()+1; } } if (selectRow != -1) { m_view->selectionModel()->setCurrentIndex(m_model->index(selectRow, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } return true; // eat event } } return QDialog::eventFilter(object, event); } QWidget* SessionChooserDialog::mainWidget() const { return m_mainWidget; } void SessionChooserDialog::deleteButtonPressed() { if(m_deleteCandidateRow == -1) return; QModelIndex uuidIndex = m_model->index(m_deleteCandidateRow, 0); QModelIndex sessionNameIndex = m_model->index(m_deleteCandidateRow, 3); const QString uuid = m_model->data(uuidIndex, Qt::DisplayRole).toString(); const QString sessionName = m_model->data(sessionNameIndex, Qt::DisplayRole).toString(); TryLockSessionResult result = SessionController::tryLockSession( uuid ); if( !result.lock ) { - const QString errCaption = i18nc("@title", "Cannot Delete Session"); + const QString errCaption = i18nc("@title:window", "Cannot Delete Session"); QString errText = i18nc("@info", "

Cannot delete a locked session."); if( result.runInfo.holderPid != -1 ) { errText += i18nc("@info", "

The session %1 is locked by %2 on %3 (PID %4).", sessionName, result.runInfo.holderApp, result.runInfo.holderHostname, result.runInfo.holderPid); } KMessageBox::error( this, errText, errCaption ); return; } const QString text = i18nc("@info", "The session %1 and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?", sessionName); - const QString caption = i18nc("@title", "Delete Session"); + const QString caption = i18nc("@title:window", "Delete Session"); const KGuiItem deleteItem = KStandardGuiItem::del(); const KGuiItem cancelItem = KStandardGuiItem::cancel(); if(KMessageBox::warningYesNo(this, text, caption, deleteItem, cancelItem) == KMessageBox::Yes) { SessionController::deleteSessionFromDisk(result.lock); m_model->removeRows( m_deleteCandidateRow, 1 ); m_deleteCandidateRow = -1; } } diff --git a/kdevplatform/shell/sessioncontroller.cpp b/kdevplatform/shell/sessioncontroller.cpp index cc816f29b2..d360de008e 100644 --- a/kdevplatform/shell/sessioncontroller.cpp +++ b/kdevplatform/shell/sessioncontroller.cpp @@ -1,707 +1,707 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = nullptr; } void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << QLatin1Char('-') + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: explicit SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(nullptr) , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { for (auto it = sessionActions.begin(), end = sessionActions.end(); it != end; ++it) { Session* s = it.key(); if( s->name() == name ) return s; } return nullptr; } Session* findSessionForId(const QString& idString) const { QUuid id(idString); for (auto it = sessionActions.begin(), end = sessionActions.end(); it != end; ++it) { Session* s = it.key(); if( s->id() == id) return s; } return nullptr; } void newSession() { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) qsrand(static_cast(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())); #endif auto* session = new Session(QUuid::createUuid().toString()); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees const auto windows = Core::self()->uiController()->controller()->mainWindows(); for (Sublime::MainWindow* window : windows) { window->close(); } #endif } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), - i18n("Rename Session"), i18n("New Session Name:"), + i18nc("@title:window", "Rename Session"), i18nc("@label:textbox", "New session name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = nullptr; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = nullptr; return; } auto* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction(QLatin1String("session_") + s->id().toString(), a); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* const q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + qApp->applicationName() + QLatin1String("/sessions/"); } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private Q_SLOTS: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject(parent) , d_ptr(new SessionControllerPrivate(this)) { Q_D(SessionController); setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), i18n("Session Manager")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction(QStringLiteral("new_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->newSession(); }); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction(QStringLiteral("rename_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->renameSession(); }); - action->setText( i18n("Rename Current Session...") ); + action->setText( i18nc("@action", "Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction(QStringLiteral("delete_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->deleteCurrentSession(); }); - action->setText( i18n("Delete Current Session...") ); + action->setText( i18nc("@action", "Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); - action->setText( i18n("Quit") ); + action->setText( i18nc("@action", "Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); d->grp = new QActionGroup( this ); connect(d->grp, &QActionGroup::triggered, this, [this] (QAction* a) { Q_D(SessionController); d->loadSessionFromAction(a); } ); } SessionController::~SessionController() = default; void SessionController::startNewSession() { Q_D(SessionController); d->newSession(); } void SessionController::cleanup() { Q_D(SessionController); if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = nullptr; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { Q_D(SessionController); QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); const auto sessionDirs = sessiondir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (const QString& s : sessionDirs) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's auto* ses = new Session(id.toString(), this); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { Q_D(const SessionController); return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { Q_D(const SessionController); return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { Q_D(SessionController); d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { Q_D(const SessionController); QList l; const auto sessions = d->sessionActions.keys(); l.reserve(sessions.size()); for(const auto* s : sessions) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { Q_D(const SessionController); QList< const KDevelop::Session* > ret; const auto sessions = d->sessionActions.keys(); ret.reserve(sessions.size()); // turn to const pointers for (const auto* s : sessions) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Q_D(SessionController); Session* s; if(name.startsWith(QLatin1Char('{'))) { s = new Session( QUuid(name).toString(), this ); }else{ #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) qsrand(static_cast(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())); #endif s = new Session( QUuid::createUuid().toString(), this ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Q_D(SessionController); Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk(DUChain::repositoryPathForSession(lock)); } void SessionController::loadDefaultSession( const QString& session ) { Q_D(SessionController); QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying do { Session* s = this->session(load); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Q_D(const SessionController); Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Q_D(SessionController); Session* origSession = session( nameOrid ); #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) qsrand(static_cast(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())); #endif QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); auto* newSession = new Session(id.toString()); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { Q_D(SessionController); unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; const auto sessionDirs = QDir(SessionControllerPrivate::sessionBaseDirectory()).entryList(QDir::AllDirs); sessionInfos.reserve(sessionDirs.size()); for (const QString& sessionId : sessionDirs) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } sessionInfos.squeeze(); return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id, bool doLocking) { return SessionLock::tryLockSession(id, doLocking); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(const QString& headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp auto* view = new QListView; auto* filter = new QLineEdit; filter->setClearButtonEnabled( true ); - filter->setPlaceholderText(i18n("Search")); + filter->setPlaceholderText(i18nc("@info:placeholder", "Search...")); auto* model = new QStandardItemModel(view); auto *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { auto* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(4); - model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); - model->setHeaderData(1, Qt::Horizontal,i18n("Contents")); - model->setHeaderData(2, Qt::Horizontal,i18n("State")); - model->setHeaderData(3, Qt::Horizontal,i18n("Name")); + model->setHeaderData(0, Qt::Horizontal,i18nc("@title:column", "Identity")); + model->setHeaderData(1, Qt::Horizontal,i18nc("@title:column", "Contents")); + model->setHeaderData(2, Qt::Horizontal,i18nc("@title:column", "State")); + model->setHeaderData(3, Qt::Horizontal,i18nc("@title:column", "Name")); view->setModel(proxy); view->setModelColumn(1); auto* filterLayout = new QHBoxLayout(); - filterLayout->addWidget(new QLabel(i18n("Filter:"))); + filterLayout->addWidget(new QLabel(i18nc("@label:textbox", "Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); for (const KDevelop::SessionInfo& si : availableSessionInfos) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); model->setItem(row, 3, new QStandardItem(si.name)); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) // krazy:exclude=crashy { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) qsrand(static_cast(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())); #endif return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { Q_D(SessionController); if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/kdevplatform/shell/sessionlock.cpp b/kdevplatform/shell/sessionlock.cpp index e7e5d8681d..12e68fa87d 100644 --- a/kdevplatform/shell/sessionlock.cpp +++ b/kdevplatform/shell/sessionlock.cpp @@ -1,222 +1,222 @@ /* * This file is part of KDevelop * Copyright 2013 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "sessionlock.h" #include "debug.h" #include "sessioncontroller.h" #include #include #include #include #include namespace KDevelop { QString lockFileForSession( const QString& id ) { return SessionController::sessionDirectory( id ) + QLatin1String("/lock"); } QString dBusServiceNameForSession( const QString& id ) { // We remove starting "{" and ending "}" from the string UUID representation // as D-Bus apparently doesn't allow them in service names return QLatin1String("org.kdevelop.kdevplatform-lock-") + id.midRef(1, id.size() - 2); } /// Force-removes the lock-file. void forceRemoveLockfile(const QString& lockFilename) { if( QFile::exists( lockFilename ) ) { QFile::remove( lockFilename ); } } TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking) { ///FIXME: if this is hit, someone tried to lock a non-existing session /// this should be fixed by using a proper data type distinct from /// QString for id's, i.e. QUuid or similar. Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId ))); /* * We've got two locking mechanisms here: D-Bus unique service name (based on the session id) * and a plain lockfile (QLockFile). * The latter is required to get the appname/pid of the locking instance * in case if it's stale/hanging/crashed (to make user know which PID he needs to kill). * D-Bus mechanism is the primary one. * * Since there is a kind of "logic tree", the code is a bit hard. */ const QString service = dBusServiceNameForSession( sessionId ); QDBusConnection connection = QDBusConnection::sessionBus(); QDBusConnectionInterface* connectionInterface = connection.interface(); const QString lockFilename = lockFileForSession( sessionId ); QSharedPointer lockFile(new QLockFile( lockFilename )); const bool haveDBus = connection.isConnected(); const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service ); bool lockedDBus = false; // Lock D-Bus if we can and we need to if( doLocking && canLockDBus ) { lockedDBus = connection.registerService( service ); } // Attempt to lock file, despite the possibility to do so and presence of the request (doLocking) // This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called bool lockResult = lockFile->tryLock(); SessionRunInfo runInfo; if (lockResult) { // Unlock immediately if we shouldn't have locked it if( haveDBus && !lockedDBus ) { lockFile->unlock(); } } else { // If locking failed, retrieve the lock's metadata lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp ); runInfo.isRunning = !haveDBus || !canLockDBus; if( haveDBus && lockedDBus ) { // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded forceRemoveLockfile(lockFilename); lockResult = lockFile->tryLock(); Q_ASSERT(lockResult); } } // Set the result by D-Bus status if (doLocking && (haveDBus ? lockedDBus : lockResult)) { return TryLockSessionResult(QSharedPointer(new SessionLock(sessionId, lockFile))); } else { return TryLockSessionResult(runInfo); } } QString SessionLock::id() { return m_sessionId; } SessionLock::SessionLock(const QString& sessionId, const QSharedPointer& lockFile) : m_sessionId(sessionId) , m_lockFile(lockFile) { Q_ASSERT(lockFile->isLocked()); } SessionLock::~SessionLock() { m_lockFile->unlock(); bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) ); Q_UNUSED(unregistered); } void SessionLock::removeFromDisk() { Q_ASSERT(m_lockFile->isLocked()); // unlock first to prevent warnings: "Could not remove our own lock file ..." m_lockFile->unlock(); QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively(); } QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo) { if( !runInfo.isRunning ) { return sessionId; } // try to make the locked session active { // The timeout for "ensureVisible" call // Leave it sufficiently low to avoid waiting for hung instances. static const int timeout_ms = 1000; QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId), QStringLiteral("/kdevelop/MainWindow"), QStringLiteral("org.kdevelop.MainWindow"), QStringLiteral("ensureVisible") ); QDBusMessage reply = QDBusConnection::sessionBus().call( message, QDBus::Block, timeout_ms ); if( reply.type() == QDBusMessage::ReplyMessage ) { QTextStream out(stdout); out << i18nc( "@info:shell", "Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one", runInfo.holderApp, runInfo.holderPid ) << endl; return QString(); } else { qCWarning(SHELL) << i18nc("@info:shell", "Running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); qCWarning(SHELL) << i18nc("@info:shell", "running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); } } // otherwise ask the user whether we should retry QString problemDescription = i18nc("@info", "The given application did not respond to a DBUS call, " "it may have crashed or is hanging."); QString problemHeader; if( runInfo.holderPid != -1 ) { problemHeader = i18nc("@info", "Failed to lock the session %1, " "already locked by %2 on %3 (PID %4).", sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid); } else { problemHeader = i18nc("@info", "Failed to lock the session %1 (lock-file unavailable).", sessionName); } QString problemResolution = i18nc("@info", "

Please, close the offending application instance " "or choose another session to launch.

"); QString errmsg = QLatin1String("

") + problemHeader + QLatin1String("
") + problemDescription + QLatin1String("

") + problemResolution; KGuiItem retry = KStandardGuiItem::cont(); - retry.setText(i18nc("@action:button", "Retry startup")); + retry.setText(i18nc("@action:button", "Retry Startup")); KGuiItem choose = KStandardGuiItem::configure(); - choose.setText(i18nc("@action:button", "Choose another session")); + choose.setText(i18nc("@action:button", "Choose Another Session")); KGuiItem cancel = KStandardGuiItem::quit(); int ret = KMessageBox::warningYesNoCancel(nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel); switch( ret ) { case KMessageBox::Yes: return sessionId; case KMessageBox::No: { QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.", sessionName); return SessionController::showSessionChooserDialog(errmsg); } case KMessageBox::Cancel: default: break; } return QString(); } } diff --git a/kdevplatform/shell/settings/bgpreferences.ui b/kdevplatform/shell/settings/bgpreferences.ui index b97053a5c8..564784f1bd 100644 --- a/kdevplatform/shell/settings/bgpreferences.ui +++ b/kdevplatform/shell/settings/bgpreferences.ui @@ -1,104 +1,104 @@ BGPreferences 0 0 475 402 0 - <p>Enables or disables the background parser.<br>If unsure, leave it enabled. Disabling the background parser will disable large parts of KDevelop's functionality.</p> + <p>Enables or disables the background parser.<br>If unsure, leave it enabled. Disabling the background parser will disable large parts of KDevelop's functionality.</p> - Enable Background Parser + Enable Background Parser true true - The time to wait before the document is re-analyzed when you edit it. + The time to wait before the document is re-analyzed when you edit it. - Delay: + Delay: - The time to wait before the document is re-analyzed when you edit it. + The time to wait before the document is re-analyzed when you edit it. - ms + ms 5000 500 - The maximum number of parallel instances the background parser uses. If unsure, select 1 or 2. + The maximum number of parallel instances the background parser uses. If unsure, select 1 or 2. - Maximum number of threads: + Maximum number of threads: - The maximum number of parallel instances the background parser uses. If unsure, select 1 or 2. + The maximum number of parallel instances the background parser uses. If unsure, select 1 or 2. - threads + threads 1 32 Qt::Vertical 20 40 diff --git a/kdevplatform/shell/settings/editstyledialog.ui b/kdevplatform/shell/settings/editstyledialog.ui index 216096e3b4..b56aa33277 100644 --- a/kdevplatform/shell/settings/editstyledialog.ui +++ b/kdevplatform/shell/settings/editstyledialog.ui @@ -1,66 +1,66 @@ EditStyle 0 0 597 300 0 0 0 0 0 0 - Preview: + Preview: 2 0 300 0 diff --git a/kdevplatform/shell/settings/environmentprofilemodel.cpp b/kdevplatform/shell/settings/environmentprofilemodel.cpp index 4dbb6c5b23..7a9df70d7d 100644 --- a/kdevplatform/shell/settings/environmentprofilemodel.cpp +++ b/kdevplatform/shell/settings/environmentprofilemodel.cpp @@ -1,238 +1,238 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat Copyright 2017 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentprofilemodel.h" #include "environmentprofilelistmodel.h" #include #include #include using namespace KDevelop; EnvironmentProfileModel::EnvironmentProfileModel(EnvironmentProfileListModel* profileListModel, QObject* parent) : QAbstractTableModel(parent) , m_profileListModel(profileListModel) { connect(m_profileListModel, &EnvironmentProfileListModel::profileAboutToBeRemoved, this, &EnvironmentProfileModel::onProfileAboutToBeRemoved); } int EnvironmentProfileModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_varsByIndex.count(); } int EnvironmentProfileModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return 2; } Qt::ItemFlags EnvironmentProfileModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return (Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled); } QVariant EnvironmentProfileModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() < 0 || index.column() >= columnCount(QModelIndex()) || m_currentProfileName.isEmpty()) { return {}; } const auto variable = m_varsByIndex[index.row()]; if (role == VariableRole) { return variable; } if (role == ValueRole) { const auto& variables = m_profileListModel->variables(m_currentProfileName); return variables.value(variable); } if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.column() == VariableColumn) { return variable; } const auto& variables = m_profileListModel->variables(m_currentProfileName); return variables.value(variable); } return {}; } QVariant EnvironmentProfileModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section < 0 || section >= columnCount(QModelIndex()) || orientation != Qt::Horizontal || role != Qt::DisplayRole) { return {}; } if (section == VariableColumn) { - return i18n("Variable"); + return i18nc("@title:column", "Variable"); } - return i18n("Value"); + return i18nc("@title:column", "Value"); } bool EnvironmentProfileModel::setData(const QModelIndex& index, const QVariant& data, int role) { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() < 0 || index.column() >= columnCount(QModelIndex()) || m_currentProfileName.isEmpty()) { return false; } if (role == Qt::EditRole) { auto& variables = m_profileListModel->variables(m_currentProfileName); if (index.column() == VariableColumn) { const QString oldVariable = m_varsByIndex[index.row()]; const QString value = variables.take(oldVariable); const QString newVariable = data.toString(); variables.insert(newVariable, value); m_varsByIndex[index.row()] = newVariable; } else { variables.insert(m_varsByIndex[index.row()], data.toString()); } emit dataChanged(index, index); } return true; } void EnvironmentProfileModel::addVariable(const QString& variableName, const QString& value) { if (m_currentProfileName.isEmpty()) { return; } const int pos = m_varsByIndex.indexOf(variableName); if (pos != -1) { return; // No duplicates, first value } auto& variables = m_profileListModel->variables(m_currentProfileName); const int insertPos = rowCount(); beginInsertRows(QModelIndex(), insertPos, insertPos); m_varsByIndex << variableName; variables.insert(variableName, value); endInsertRows(); } void EnvironmentProfileModel::removeVariables(const QStringList& variableNames) { for (const auto& variableName : variableNames) { removeVariable(variableName); } } void EnvironmentProfileModel::removeVariable(const QString& variableName) { if (m_currentProfileName.isEmpty()) { return; } const int pos = m_varsByIndex.indexOf(variableName); if (pos == -1) { return; } auto& variables = m_profileListModel->variables(m_currentProfileName); beginRemoveRows(QModelIndex(), pos, pos); m_varsByIndex.removeAt(pos); variables.remove(variableName); endRemoveRows(); } void EnvironmentProfileModel::setCurrentProfile(const QString& profileName) { if (profileName == m_currentProfileName) { return; } beginResetModel(); m_currentProfileName = profileName; m_varsByIndex.clear(); if (!m_currentProfileName.isEmpty()) { const auto& variables = m_profileListModel->variables(m_currentProfileName); m_varsByIndex.reserve(variables.size()); const auto endIt = variables.constEnd(); for (auto it = variables.constBegin(); it != endIt; ++it) { m_varsByIndex << it.key(); } } endResetModel(); } void EnvironmentProfileModel::setVariablesFromString(const QString& plainText) { if (m_currentProfileName.isEmpty()) { return; } beginResetModel(); auto& variables = m_profileListModel->variables(m_currentProfileName); variables.clear(); m_varsByIndex.clear(); const auto lines = plainText.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (const auto& line : lines) { const int pos = line.indexOf(QLatin1Char('=')); // has a = and at least 1 char if (pos < 0) { continue; } const QString variableName = line.leftRef(pos).trimmed().toString(); if (variableName.isEmpty()) { continue; } const QString value = line.midRef(pos+1).trimmed().toString(); m_varsByIndex << variableName; variables.insert(variableName, value); } endResetModel(); } void EnvironmentProfileModel::onProfileAboutToBeRemoved(const QString& profileName) { if (m_currentProfileName == profileName) { setCurrentProfile(QString()); } } diff --git a/kdevplatform/shell/settings/environmentwidget.cpp b/kdevplatform/shell/settings/environmentwidget.cpp index eb98a87c4d..73074683bc 100644 --- a/kdevplatform/shell/settings/environmentwidget.cpp +++ b/kdevplatform/shell/settings/environmentwidget.cpp @@ -1,341 +1,341 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Dukju Ahn Copyright 2008 Andreas Pakuat Copyright 2017 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "environmentprofilelistmodel.h" #include "environmentprofilemodel.h" #include "placeholderitemproxymodel.h" #include "debug.h" using namespace KDevelop; class ProfileNameValidator : public QValidator { Q_OBJECT public: explicit ProfileNameValidator(EnvironmentProfileListModel* environmentProfileListModel, QObject* parent = nullptr); QValidator::State validate(QString& input, int& pos) const override; private: const EnvironmentProfileListModel* const m_environmentProfileListModel; }; ProfileNameValidator::ProfileNameValidator(EnvironmentProfileListModel* environmentProfileListModel, QObject* parent) : QValidator(parent) , m_environmentProfileListModel(environmentProfileListModel) { } QValidator::State ProfileNameValidator::validate(QString& input, int& pos) const { Q_UNUSED(pos); if (input.isEmpty()) { return QValidator::Intermediate; } if (m_environmentProfileListModel->hasProfile(input)) { return QValidator::Intermediate; } return QValidator::Acceptable; } EnvironmentWidget::EnvironmentWidget( QWidget *parent ) : QWidget(parent) , m_environmentProfileListModel(new EnvironmentProfileListModel(this)) , m_environmentProfileModel(new EnvironmentProfileModel(m_environmentProfileListModel, this)) , m_proxyModel(new QSortFilterProxyModel(this)) { // setup ui ui.setupUi( this ); ui.profileSelect->setModel(m_environmentProfileListModel); m_proxyModel->setSourceModel(m_environmentProfileModel); auto* topProxyModel = new PlaceholderItemProxyModel(this); topProxyModel->setSourceModel(m_proxyModel); - topProxyModel->setColumnHint(0, i18n("Enter variable...")); + topProxyModel->setColumnHint(0, i18nc("@info:placeholder", "Enter variable...")); connect(topProxyModel, &PlaceholderItemProxyModel::dataInserted, this, &EnvironmentWidget::onVariableInserted); ui.variableTable->setModel( topProxyModel ); ui.variableTable->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); ui.variableTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); ui.removeVariableButton->setShortcut(Qt::Key_Delete); connect(ui.removeVariableButton, &QPushButton::clicked, this, &EnvironmentWidget::removeSelectedVariables); connect(ui.batchModeEditButton, &QPushButton::clicked, this, &EnvironmentWidget::batchModeEditButtonClicked); connect(ui.cloneProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::cloneSelectedProfile); connect(ui.addProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::addProfile); connect(ui.removeProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::removeSelectedProfile); connect(ui.setAsDefaultProfileButton, &QPushButton::clicked, this, &EnvironmentWidget::setSelectedProfileAsDefault); connect(ui.profileSelect, QOverload::of(&KComboBox::currentIndexChanged), this, &EnvironmentWidget::onSelectedProfileChanged); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::defaultProfileChanged, this, &EnvironmentWidget::onDefaultProfileChanged); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::rowsInserted, this, &EnvironmentWidget::changed); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::rowsRemoved, this, &EnvironmentWidget::changed); connect(m_environmentProfileListModel, &EnvironmentProfileListModel::defaultProfileChanged, this, &EnvironmentWidget::changed); connect(ui.variableTable->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsInserted, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsRemoved, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::modelReset, this, &EnvironmentWidget::updateDeleteVariableButton); connect(m_environmentProfileModel, &EnvironmentProfileModel::dataChanged, this, &EnvironmentWidget::changed); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsInserted, this, &EnvironmentWidget::changed); connect(m_environmentProfileModel, &EnvironmentProfileModel::rowsRemoved, this, &EnvironmentWidget::changed); } void EnvironmentWidget::selectProfile(const QString& profileName) { const int profileIndex = m_environmentProfileListModel->profileIndex(profileName); if (profileIndex < 0) { return; } ui.profileSelect->setCurrentIndex(profileIndex); } void EnvironmentWidget::updateDeleteVariableButton() { const auto selectedRows = ui.variableTable->selectionModel()->selectedRows(); ui.removeVariableButton->setEnabled(!selectedRows.isEmpty()); } void EnvironmentWidget::setSelectedProfileAsDefault() { const int selectedIndex = ui.profileSelect->currentIndex(); m_environmentProfileListModel->setDefaultProfile(selectedIndex); } void EnvironmentWidget::loadSettings( KConfig* config ) { qCDebug(SHELL) << "Loading profiles from config"; m_environmentProfileListModel->loadFromConfig(config); const int defaultProfileIndex = m_environmentProfileListModel->defaultProfileIndex(); ui.profileSelect->setCurrentIndex(defaultProfileIndex); } void EnvironmentWidget::saveSettings( KConfig* config ) { m_environmentProfileListModel->saveToConfig(config); } void EnvironmentWidget::defaults( KConfig* config ) { loadSettings( config ); } QString EnvironmentWidget::askNewProfileName(const QString& defaultName) { ScopedDialog dialog(this); - dialog->setWindowTitle(i18n("Enter Name of New Environment Profile")); + dialog->setWindowTitle(i18nc("@title:window", "Enter Name of New Environment Profile")); auto *layout = new QVBoxLayout(dialog); auto editLayout = new QHBoxLayout; - auto label = new QLabel(i18n("Name:")); + auto label = new QLabel(i18nc("@label:textbox", "Name:")); editLayout->addWidget(label); auto edit = new QLineEdit; editLayout->addWidget(edit); layout->addLayout(editLayout); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(false); okButton->setDefault(true); dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); layout->addWidget(buttonBox); auto validator = new ProfileNameValidator(m_environmentProfileListModel, dialog); connect(edit, &QLineEdit::textChanged, validator, [validator, okButton](const QString& text) { int pos; QString t(text); const bool isValidProfileName = (validator->validate(t, pos) == QValidator::Acceptable); okButton->setEnabled(isValidProfileName); }); edit->setText(defaultName); edit->selectAll(); if (dialog->exec() != QDialog::Accepted) { return {}; } return edit->text(); } void EnvironmentWidget::removeSelectedVariables() { const auto selectedRows = ui.variableTable->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) { return; } QStringList variables; variables.reserve(selectedRows.size()); for (const auto& idx : selectedRows) { const QString variable = idx.data(EnvironmentProfileModel::VariableRole).toString(); variables << variable; } m_environmentProfileModel->removeVariables(variables); } void EnvironmentWidget::onVariableInserted(int column, const QVariant& value) { Q_UNUSED(column); m_environmentProfileModel->addVariable(value.toString(), QString()); } void EnvironmentWidget::batchModeEditButtonClicked() { ScopedDialog dialog(this); - dialog->setWindowTitle( i18n( "Batch Edit Mode" ) ); + dialog->setWindowTitle( i18nc("@title:window", "Batch Edit Mode" ) ); auto *layout = new QVBoxLayout(dialog); auto edit = new QPlainTextEdit; edit->setPlaceholderText(QStringLiteral("VARIABLE1=VALUE1\nVARIABLE2=VALUE2")); QString text; for (int i = 0; i < m_proxyModel->rowCount(); ++i) { const auto variable = m_proxyModel->index(i, EnvironmentProfileModel::VariableColumn).data().toString(); const auto value = m_proxyModel->index(i, EnvironmentProfileModel::ValueColumn).data().toString(); text.append(variable + QLatin1Char('=') + value + QLatin1Char('\n')); } edit->setPlainText(text); layout->addWidget( edit ); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); layout->addWidget(buttonBox); dialog->resize(600, 400); if ( dialog->exec() != QDialog::Accepted ) { return; } m_environmentProfileModel->setVariablesFromString(edit->toPlainText()); } void EnvironmentWidget::addProfile() { const auto profileName = askNewProfileName(QString()); if (profileName.isEmpty()) { return; } const int profileIndex = m_environmentProfileListModel->addProfile(profileName); ui.profileSelect->setCurrentIndex(profileIndex); ui.variableTable->setFocus(Qt::OtherFocusReason); } void EnvironmentWidget::cloneSelectedProfile() { const int currentIndex = ui.profileSelect->currentIndex(); const auto currentProfileName = m_environmentProfileListModel->profileName(currentIndex); // pass original name as starting name, as the user might want to enter a variant of it const auto profileName = askNewProfileName(currentProfileName); if (profileName.isEmpty()) { return; } const int profileIndex = m_environmentProfileListModel->cloneProfile(profileName, currentProfileName); ui.profileSelect->setCurrentIndex(profileIndex); ui.variableTable->setFocus(Qt::OtherFocusReason); } void EnvironmentWidget::removeSelectedProfile() { if (ui.profileSelect->count() <= 1) { return; } const int selectedProfileIndex = ui.profileSelect->currentIndex(); m_environmentProfileListModel->removeProfile(selectedProfileIndex); const int defaultProfileIndex = m_environmentProfileListModel->defaultProfileIndex(); ui.profileSelect->setCurrentIndex(defaultProfileIndex); } void EnvironmentWidget::onDefaultProfileChanged(int defaultProfileIndex) { const int selectedProfileIndex = ui.profileSelect->currentIndex(); const bool isDefaultProfile = (defaultProfileIndex == selectedProfileIndex); ui.removeProfileButton->setEnabled(ui.profileSelect->count() > 1 && !isDefaultProfile); ui.setAsDefaultProfileButton->setEnabled(!isDefaultProfile); } void EnvironmentWidget::onSelectedProfileChanged(int selectedProfileIndex) { const auto selectedProfileName = m_environmentProfileListModel->profileName(selectedProfileIndex); m_environmentProfileModel->setCurrentProfile(selectedProfileName); const bool isDefaultProfile = (m_environmentProfileListModel->defaultProfileIndex() == selectedProfileIndex); ui.removeProfileButton->setEnabled(ui.profileSelect->count() > 1 && !isDefaultProfile); ui.setAsDefaultProfileButton->setEnabled(!isDefaultProfile); } #include "environmentwidget.moc" diff --git a/kdevplatform/shell/settings/environmentwidget.ui b/kdevplatform/shell/settings/environmentwidget.ui index 22e6b2bf43..8f4ae89a08 100644 --- a/kdevplatform/shell/settings/environmentwidget.ui +++ b/kdevplatform/shell/settings/environmentwidget.ui @@ -1,163 +1,163 @@ EnvironmentWidget 0 0 704 480 0 - Environment profile: + Environment profile: 1 0 - Add profile... + Add profile... - Clone profile... + Clone profile... - Remove profile + Remove profile - Set as default + Set as default 0 2 QAbstractItemView::ExtendedSelection QAbstractItemView::SelectRows true false false 0 0 - Remove variable + Remove variable 0 0 - Batch Edit Mode + Batch edit mode Qt::Vertical 20 40 KComboBox QComboBox
KComboBox
diff --git a/kdevplatform/shell/settings/languagepreferences.cpp b/kdevplatform/shell/settings/languagepreferences.cpp index a50ef02528..365589d110 100644 --- a/kdevplatform/shell/settings/languagepreferences.cpp +++ b/kdevplatform/shell/settings/languagepreferences.cpp @@ -1,98 +1,98 @@ /* KDevelop Project Settings * * Copyright 2006 Matt Rogers * Copyright 2007 Hamish Rodda * Copyright 2009 David Nolden * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "languagepreferences.h" #include #include #include #include #include #include #include #include "../completionsettings.h" #include "../core.h" #include "languageconfig.h" #include "ui_languagepreferences.h" using namespace KTextEditor; namespace KDevelop { LanguagePreferences::LanguagePreferences(QWidget* parent) : ConfigPage(nullptr, LanguageConfig::self(), parent) { preferencesDialog = new Ui::LanguagePreferences; preferencesDialog->setupUi(this); - preferencesDialog->kcfg_minFilesForSimplifiedParsing->setSuffix(ki18np(" file", " files")); + preferencesDialog->kcfg_minFilesForSimplifiedParsing->setSuffix(ki18ncp("@item:valuesuffix", " file", " files")); } void LanguagePreferences::notifySettingsChanged() { auto& settings(static_cast(*ICore::self()->languageController()->completionSettings())); settings.emitChanged(); } LanguagePreferences::~LanguagePreferences( ) { delete preferencesDialog; } void LanguagePreferences::apply() { ConfigPage::apply(); const auto documents = Core::self()->documentController()->openDocuments(); for (KDevelop::IDocument* doc : documents) { if (Document* textDoc = doc->textDocument()) { const auto views = textDoc->views(); for (View* view : views) { if (auto* cc = qobject_cast(view)) { cc->setAutomaticInvocationEnabled(preferencesDialog->kcfg_automaticInvocation->isChecked()); } } } } notifySettingsChanged(); } QString LanguagePreferences::name() const { return i18n("Language Support"); } QString LanguagePreferences::fullName() const { return i18n("Configure Code-Completion and Semantic Highlighting"); } QIcon LanguagePreferences::icon() const { return QIcon::fromTheme(QStringLiteral("page-zoom")); } } diff --git a/kdevplatform/shell/settings/languagepreferences.ui b/kdevplatform/shell/settings/languagepreferences.ui index f7ad834fd4..4b401a0dd3 100644 --- a/kdevplatform/shell/settings/languagepreferences.ui +++ b/kdevplatform/shell/settings/languagepreferences.ui @@ -1,370 +1,370 @@ LanguagePreferences 0 0 599 592 0 0 0 0 - Code Completion + Code Completion - If disabled, the code completion widget will never show automatically. + If disabled, the code completion widget will never show automatically. - Choose whether to display additional information for the currently selected code completion item. + Choose whether to display additional information for the currently selected code completion item. <p>The code completion UI has a minimal mode and a detailed mode.<br>Choose in what cases full code completion will be displayed.</p> 1 - Never + Never - When Invoked Manually + When Invoked Manually - Always + Always <p>The code completion UI has a minimal mode and a detailed mode.<br>Choose in what cases full code completion will be displayed.</p> - Detailed completion: + Detailed completion: - If disabled, the code completion widget will never show automatically. + If disabled, the code completion widget will never show automatically. - Enable automatic invocation: + Enable automatic invocation: - Choose whether to display additional information for the currently selected codecompletion item. + Choose whether to display additional information for the currently selected codecompletion item. - Additional information for current item: + Additional information for current item: - Semantic Code Highlighting + Semantic Code Highlighting QFormLayout::ExpandingFieldsGrow <p>This setting decides about the intensity of colors for local variables, for example function arguments, variables and the like.</p> - Local colorization intensity: + Local colorization intensity: kcfg_localColorization <p>This setting decides about the intensity of colors for local variables, for example function arguments, variables and the like.</p> 255 3 25 170 Qt::Horizontal <p>This settings lets you change the intensity of globally accessible types, for example classes, methods, functions etc.</p> - Global colorization intensity: + Global colorization intensity: kcfg_localColorization <p>This settings lets you change the intensity of globally accessible types, for example classes, methods, functions etc.</p> 255 3 25 255 Qt::Horizontal <p>Highlight semantic problems, such as non-existent or inaccessible declarations.</p> - Highlight semantic problems: + Highlight semantic problems: <p>Highlight semantic problems, such as non-existent or inaccessible declarations.</p> <p>When enabled, lines with errors will get additionally highlighted and their positions get marked in the scrollbar.</p> - Highlight problematic lines: + Highlight problematic lines: <p>When enabled, lines with errors will get additionally highlighted and their positions get marked in the scrollbar.</p> - Space-separated list of words that make a comment represent a TODO item + Space-separated list of words that make a comment represent a TODO item - TODO marker words: + TODO marker words: - Space-separated list of words that make a comment represent a TODO item. + Space-separated list of words that make a comment represent a TODO item. <html><head/><body><p>Use bold font for declarations such as classes, functions, local variables, etc.</p></body></html> - Bold font for declarations: + Bold font for declarations: - None + None - Errors + Errors - Warnings and Errors + Warnings and Errors - Hints, Warnings and Errors + Hints, Warnings and Errors - Problems shown as inline notes: + Problems shown as inline notes: kcfg_problemInlineNotesLevel kcfg_localColorization label_4 kcfg_highlightSemanticProblems label_5 kcfg_highlightProblematicLines label_3 kcfg_todoMarkerWords kcfg_globalColorization globalColorizationLabel localColorizationLabel boldDeclarationsLabel kcfg_boldDeclarations label_8 - Project Parsing + Project Parsing QFormLayout::ExpandingFieldsGrow <p>When a project contains more files than this number, the project will be parsed in simplified mode, increasing the efficiency by gathering less information. Global code navigation and quickopen capabilities will be reduced.</p> - Minimum project size for simplified parsing: + Minimum project size for simplified parsing: 0 0 <p>When a project contains more files than this number, the project will be parsed in simplified mode, increasing the efficiency by gathering less information. Global code navigation and quickopen capabilities will be reduced.</p> 99999999 1000 Qt::Vertical 20 40 diff --git a/kdevplatform/shell/settings/projectpreferences.ui b/kdevplatform/shell/settings/projectpreferences.ui index cd436c6d69..4f0c4dbb8a 100644 --- a/kdevplatform/shell/settings/projectpreferences.ui +++ b/kdevplatform/shell/settings/projectpreferences.ui @@ -1,88 +1,88 @@ ProjectPreferences 0 0 597 247 0 - Select the base directory where new projects are to be created. + Select the base directory where new projects are to be created - Projects base directory: + Projects base directory: kcfg_projectsBaseDirectory - Select the base directory where new projects are to be created. + Select the base directory where new projects are to be created KFile::Directory|KFile::ExistingOnly|KFile::LocalOnly - Parse all files in a project after the project has been opened. + Parse all files in a project after the project has been opened - Schedule all project files for parsing + Schedule all project files for parsing - Save all modified documents when the "build" action is triggered. + Save all modified documents when the "build" action is triggered - Save all documents before building + Save all documents before building Qt::Vertical 403 147 KUrlRequester QWidget
KUrlRequester
diff --git a/kdevplatform/shell/settings/sourceformattersettings.ui b/kdevplatform/shell/settings/sourceformattersettings.ui index 397f742e62..1885440ef3 100644 --- a/kdevplatform/shell/settings/sourceformattersettings.ui +++ b/kdevplatform/shell/settings/sourceformattersettings.ui @@ -1,76 +1,76 @@ SourceFormatterSettingsUI 0 0 636 632 0 - General + General - Override the editor indentation mode according to the formatting style for documents without Kate modeline. + Override the editor indentation mode according to the formatting style for documents without Kate modeline. - Override Kate Indentation Mode + Override Kate Indentation Mode - Add a Kate modeline according to the formatting style to formatted documents. + Add a Kate modeline according to the formatting style to formatted documents. - Add Kate Modeline + Add Kate Modeline 200 129 - Formatting Styles + Formatting Styles KDevelop::SourceFormatterSelectionEdit QWidget
sourceformatterselectionedit.h
1
diff --git a/kdevplatform/shell/settings/templatepage.ui b/kdevplatform/shell/settings/templatepage.ui index 1b964d8d78..397dddcbc9 100644 --- a/kdevplatform/shell/settings/templatepage.ui +++ b/kdevplatform/shell/settings/templatepage.ui @@ -1,88 +1,88 @@ TemplatePage 0 0 548 300 false - Load Template From File + Load Template From File - Get More Templates + Get More Templates - Share Templates + Share Templates - Clone Template + Clone Template Qt::Vertical 20 40 diff --git a/kdevplatform/shell/settings/uiconfig.ui b/kdevplatform/shell/settings/uiconfig.ui index ed02382af1..c46d5b0925 100644 --- a/kdevplatform/shell/settings/uiconfig.ui +++ b/kdevplatform/shell/settings/uiconfig.ui @@ -1,224 +1,224 @@ UiConfig 0 0 521 399 User Interface 0 - Dock Window Behavior + Dock Window Behavior <p>Controls whether the bottom left corner is occupied by the dock at the left, or by the dock at the bottom.</p> - Bottom left corner occupied by: + Bottom left corner occupied by: <p>Controls whether the bottom right corner is occupied by the dock at the right, or by the dock at the bottom.</p> - Bottom right corner occupied by: + Bottom right corner occupied by: <p>Controls whether the bottom left corner is occupied by the dock at the left, or by the dock at the bottom.</p> - Left Dock + Left Dock - Bottom Dock + Bottom Dock <p>Controls whether the bottom right corner is occupied by the dock at the right, or by the dock at the bottom.</p> - Right Dock + Right Dock - Bottom Dock + Bottom Dock - Tabbed Browsing + Tabbed Browsing - Controls whether to open new tabs next to the active one. + Controls whether to open new tabs next to the active one. - Open new tab after current + Open new tab after current true - When enabled, plugins can group related files side by side. + When enabled, plugins can group related files side by side. For example, a header file will be opened next to the implementation file. - Arrange related documents side by side + Arrange related documents side by side - Enables or disables the display of the tab bar at the top of the editor window. + Enables or disables the display of the tab bar at the top of the editor window. - Show tabs + Show tabs true - Enables or disables the display of a close button in each tab in the tab bar at the top of the editor window. + Enables or disables the display of a close button in each tab in the tab bar at the top of the editor window. - Show close buttons on tabs + Show close buttons on tabs true - Coloring + Coloring - When enabled, tabs and other widgets are colored based on the project affiliation + When enabled, tabs and other widgets are colored based on the project affiliation - Color widgets based on the project affiliation + Color widgets based on the project affiliation true Qt::Vertical kcfg_TabBarVisibility toggled(bool) kcfg_TabBarOpenAfterCurrent setEnabled(bool) 260 133 260 158 kcfg_TabBarVisibility toggled(bool) kcfg_TabBarArrangeBuddies setEnabled(bool) 260 133 260 183 kcfg_TabBarVisibility toggled(bool) kcfg_CloseButtonsOnTabs setEnabled(bool) 260 133 260 158 diff --git a/kdevplatform/shell/sourceformattercontroller.cpp b/kdevplatform/shell/sourceformattercontroller.cpp index 59e700c54e..f97dc9e7de 100644 --- a/kdevplatform/shell/sourceformattercontroller.cpp +++ b/kdevplatform/shell/sourceformattercontroller.cpp @@ -1,844 +1,844 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattercontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "debug.h" #include "plugincontroller.h" #include "sourceformatterjob.h" #include "textdocument.h" namespace { namespace Strings { QString SourceFormatter() { return QStringLiteral("SourceFormatter"); } QString UseDefault() { return QStringLiteral("UseDefault"); } } } namespace KDevelop { using TextStreamFunction = QTextStream& (*)(QTextStream&); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) constexpr TextStreamFunction endl = Qt::endl; #else constexpr TextStreamFunction endl = ::endl; #endif class SourceFormatterControllerPrivate { public: // cache of formatter plugins, to avoid querying plugincontroller QVector sourceFormatters; // GUI actions QAction* formatTextAction; QAction* formatFilesAction; QAction* formatLine; QList prjItems; QList urls; bool enabled = true; ISourceFormatter* formatterForConfigEntry(const QString& entry, const QString& mimename) const; }; ISourceFormatter* SourceFormatterControllerPrivate::formatterForConfigEntry(const QString& entry, const QString& mimename) const { QStringList formatterinfo = entry.split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mimename << "current value:" << entry; } auto it = std::find_if(sourceFormatters.begin(), sourceFormatters.end(), [&](ISourceFormatter* iformatter) { return (iformatter->name() == formatterinfo.first()); }); return (it != sourceFormatters.end()) ? *it : nullptr; } QString SourceFormatterController::kateModeLineConfigKey() { return QStringLiteral("ModelinesEnabled"); } QString SourceFormatterController::kateOverrideIndentationConfigKey() { return QStringLiteral("OverrideKateIndentation"); } QString SourceFormatterController::styleCaptionKey() { return QStringLiteral("Caption"); } QString SourceFormatterController::styleShowPreviewKey() { return QStringLiteral("ShowPreview"); } QString SourceFormatterController::styleContentKey() { return QStringLiteral("Content"); } QString SourceFormatterController::styleMimeTypesKey() { return QStringLiteral("MimeTypes"); } QString SourceFormatterController::styleSampleKey() { return QStringLiteral("StyleSample"); } SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent) , d_ptr(new SourceFormatterControllerPrivate) { Q_D(SourceFormatterController); setObjectName(QStringLiteral("SourceFormatterController")); setComponentName(QStringLiteral("kdevsourceformatter"), i18n("Source Formatter")); setXMLFile(QStringLiteral("kdevsourceformatter.rc")); if (Core::self()->setupFlags() & Core::NoUi) return; d->formatTextAction = actionCollection()->addAction(QStringLiteral("edit_reformat_source")); - d->formatTextAction->setText(i18n("&Reformat Source")); - d->formatTextAction->setToolTip(i18n("Reformat source using AStyle")); - d->formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); + d->formatTextAction->setText(i18nc("@action", "&Reformat Source")); + d->formatTextAction->setToolTip(i18nc("@info:tooltip", "Reformat source using AStyle")); + d->formatTextAction->setWhatsThis(i18nc("@info:whatsthis", "Source reformatting functionality using astyle library.")); d->formatTextAction->setEnabled(false); connect(d->formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource); d->formatLine = actionCollection()->addAction(QStringLiteral("edit_reformat_line")); - d->formatLine->setText(i18n("Reformat Line")); - d->formatLine->setToolTip(i18n("Reformat current line using AStyle")); - d->formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); + d->formatLine->setText(i18nc("@action", "Reformat Line")); + d->formatLine->setToolTip(i18nc("@info:tooltip", "Reformat current line using AStyle")); + d->formatLine->setWhatsThis(i18nc("@info:whatsthis", "Source reformatting of line under cursor using astyle library.")); d->formatLine->setEnabled(false); connect(d->formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine); d->formatFilesAction = actionCollection()->addAction(QStringLiteral("tools_astyle")); - d->formatFilesAction->setText(i18n("Reformat Files...")); - d->formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); - d->formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); + d->formatFilesAction->setText(i18nc("@action", "Reformat Files...")); + d->formatFilesAction->setToolTip(i18nc("@info:tooltip", "Format file(s) using the current theme")); + d->formatFilesAction->setWhatsThis(i18nc("@info:whatsthis", "Formatting functionality using astyle library.")); d->formatFilesAction->setEnabled(false); connect(d->formatFilesAction, &QAction::triggered, this, QOverload<>::of(&SourceFormatterController::formatFiles)); connect(Core::self()->pluginController(), &IPluginController::pluginLoaded, this, &SourceFormatterController::pluginLoaded); connect(Core::self()->pluginController(), &IPluginController::unloadingPlugin, this, &SourceFormatterController::unloadingPlugin); // connect to both documentActivated & documentClosed, // otherwise we miss when the last document was closed connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::updateFormatTextAction); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &SourceFormatterController::updateFormatTextAction); qRegisterMetaType>(); connect(Core::self()->documentController(), &IDocumentController::documentLoaded, // Use a queued connection, because otherwise the view is not yet fully set up // but wrap the document in a smart pointer to guard against crashes when it // gets deleted in the meantime this, [this](IDocument *doc) { const auto textDoc = QPointer(dynamic_cast(doc)); QMetaObject::invokeMethod(this, "documentLoaded", Qt::QueuedConnection, Q_ARG(QPointer, textDoc)); }); connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &SourceFormatterController::projectOpened); updateFormatTextAction(); } void SourceFormatterController::documentLoaded(const QPointer& doc) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc || !doc->textDocument()) { return; } const auto url = doc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(url, mime), url); } void SourceFormatterController::projectOpened(const IProject* project) { Q_D(SourceFormatterController); // Adapt the indentation mode if a project was just opened. Otherwise if a document // is loaded before its project, it might not have the correct indentation mode set. auto config = project->projectConfiguration()->group(Strings::SourceFormatter()); if (!config.isValid() || config.readEntry(Strings::UseDefault(), true)) { return; } QHash formatters; const auto documents = ICore::self()->documentController()->openDocuments(); for (const KDevelop::IDocument* doc : documents) { if (project->inProject(IndexedString(doc->url()))) { const QString mimename = QMimeDatabase().mimeTypeForUrl(doc->url()).name(); auto it = formatters.find(mimename); if (it == formatters.end()) { const auto entry = config.readEntry(mimename, QString()); it = formatters.insert(mimename, entry.isEmpty() ? nullptr : d->formatterForConfigEntry(entry, mimename)); } if (it.value()) { adaptEditorIndentationMode(doc->textDocument(), it.value(), doc->url()); } } } } void SourceFormatterController::pluginLoaded(IPlugin* plugin) { Q_D(SourceFormatterController); auto* sourceFormatter = plugin->extension(); if (!sourceFormatter) { return; } d->sourceFormatters << sourceFormatter; resetUi(); emit formatterLoaded(sourceFormatter); // with one plugin now added, hasFormatters turned to true, so report to listeners if (d->sourceFormatters.size() == 1) { emit hasFormattersChanged(true); } } void SourceFormatterController::unloadingPlugin(IPlugin* plugin) { Q_D(SourceFormatterController); auto* sourceFormatter = plugin->extension(); if (!sourceFormatter) { return; } const int idx = d->sourceFormatters.indexOf(sourceFormatter); Q_ASSERT(idx != -1); d->sourceFormatters.remove(idx); resetUi(); emit formatterUnloading(sourceFormatter); if (d->sourceFormatters.isEmpty()) { emit hasFormattersChanged(false); } } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForUrl(url, mime); } KConfigGroup SourceFormatterController::configForUrl(const QUrl& url) const { auto core = KDevelop::Core::self(); auto project = core->projectController()->findProjectForUrl(url); if (project) { auto config = project->projectConfiguration()->group(Strings::SourceFormatter()); if (config.isValid() && !config.readEntry(Strings::UseDefault(), true)) { return config; } } return core->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::sessionConfig() const { return KDevelop::Core::self()->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::globalConfig() const { return KSharedConfig::openConfig()->group( Strings::SourceFormatter() ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { Q_D(const SourceFormatterController); static QHash knownFormatters; const auto formatterIt = knownFormatters.constFind(mime.name()); if (formatterIt != knownFormatters.constEnd()) return *formatterIt; auto it = std::find_if(d->sourceFormatters.constBegin(), d->sourceFormatters.constEnd(), [&](ISourceFormatter* iformatter) { QSharedPointer formatter(createFormatterForPlugin(iformatter)); return (formatter->supportedMimeTypes().contains(mime.name())); }); ISourceFormatter* iformatter = (it != d->sourceFormatters.constEnd()) ? *it : nullptr; // cache result in any case knownFormatters.insert(mime.name(), iformatter); return iformatter; } static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) { s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey(), QString() ) ); s->setUsePreview( stylegrp.readEntry( SourceFormatterController::styleShowPreviewKey(), false ) ); s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey(), QString() ) ); s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey(), QStringList() ) ); s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey(), QString() ) ); } SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const { auto* formatter = new SourceFormatter(); formatter->formatter = ifmt; // Inserted a new formatter. Now fill it with styles const auto predefinedStyles = ifmt->predefinedStyles(); for (const KDevelop::SourceFormatterStyle& style : predefinedStyles) { formatter->styles[ style.name() ] = new SourceFormatterStyle(style); } KConfigGroup grp = globalConfig(); if( grp.hasGroup( ifmt->name() ) ) { KConfigGroup fmtgrp = grp.group( ifmt->name() ); const auto subgroups = fmtgrp.groupList(); for (const QString& subgroup : subgroups) { auto* s = new SourceFormatterStyle( subgroup ); KConfigGroup stylegrp = fmtgrp.group( subgroup ); populateStyleFromConfigGroup(s, stylegrp); formatter->styles[ s->name() ] = s; } } return formatter; } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl& url, const QMimeType& mime) { Q_D(SourceFormatterController); if (!d->enabled || !isMimeTypeSupported(mime)) { return nullptr; } const auto formatter = configForUrl(url).readEntry(mime.name(), QString()); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } return d->formatterForConfigEntry(formatter, mime.name()); } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits(QStringLiteral("text/x-c++src")) || mime.inherits(QStringLiteral("text/x-chdr")) || mime.inherits(QStringLiteral("text/x-c++hdr")) || mime.inherits(QStringLiteral("text/x-csrc")) || mime.inherits(QStringLiteral("text/x-java")) || mime.inherits(QStringLiteral("text/x-csharp"))) { return QStringLiteral("cstyle"); } return QStringLiteral("none"); } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline(QStringLiteral("\\s*\\n//\\s*kate:(.*)$")); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if (!configForUrl(url).readEntry(SourceFormatterController::kateModeLineConfigKey(), false) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForUrl(url, mime); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline(QStringLiteral("// kate: ") + QLatin1String("indent-mode ") + indentationMode(mime) + QLatin1String("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { const auto state = (indentation.indentationTabWidth == -1) ? QLatin1String("on") : QLatin1String("off"); modeline += QLatin1String("replace-tabs ") + state + QLatin1String("; "); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "created modeline: " << modeline; QRegExp kateModeline(QStringLiteral("^\\s*//\\s*kate:(.*)$")); bool modelinefound = false; QRegExp knownOptions(QStringLiteral("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)")); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match qCDebug(SHELL) << "Found a kate modeline: " << line; modelinefound = true; QString options = kateModeline.cap(1); const QStringList optionList = options.split(QLatin1Char(';'), QString::SkipEmptyParts); os << modeline; for (QString s : optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(QLatin1Char(' '))) s.remove(0, 1); os << s << ";"; qCDebug(SHELL) << "Found unknown option: " << s; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::updateFormatTextAction() { Q_D(SourceFormatterController); bool enabled = false; if (!d->sourceFormatters.isEmpty()) { IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument(); if (doc) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } } d->formatLine->setEnabled(enabled); d->formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); if (!idoc) return; KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter const auto url = idoc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); ISourceFormatter* formatter = formatterForUrl(url, mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode(doc, formatter, url, true); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), url, mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith(QLatin1Char('\n')) && output.endsWith(QLatin1Char('\n'))) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter const auto url = doc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); ISourceFormatter* formatter = formatterForUrl(url, mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = QLatin1Char('\n') + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime) { Q_ASSERT(doc); Q_ASSERT(formatter); qCDebug(SHELL) << "Running" << formatter->name() << "on" << doc->url(); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { const auto documents = ICore::self()->documentController()->openDocuments(); for (KDevelop::IDocument* doc : documents) { adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(doc->url()), doc->url()); } } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, const QUrl& url, bool ignoreModeline) { if (!formatter || !configForUrl(url).readEntry(SourceFormatterController::kateOverrideIndentationConfigKey(), false) || !doc) return; qCDebug(SHELL) << "adapting mode for" << url; QRegExp kateModelineWithNewline(QStringLiteral("\\s*\\n//\\s*kate:(.*)$")); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(url); if(indentation.isValid()) { struct CommandCaller { explicit CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(const QString& cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; const auto views = doc->views(); for (KTextEditor::View* view : views) { if (!command->exec(view, cmd, msg)) qCWarning(SHELL) << "setting indentation width failed: " << msg; } } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { Q_D(SourceFormatterController); if (d->prjItems.isEmpty() && d->urls.isEmpty()) return; //get a list of all files in this folder recursively QList folders; for (KDevelop::ProjectBaseItem* item : qAsConst(d->prjItems)) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) d->urls.append(item->file()->path().toUrl()); else if (item->target()) { const auto files = item->fileList(); for (KDevelop::ProjectFileItem* f : files) { d->urls.append(f->path().toUrl()); } } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); const auto folderList = item->folderList(); for (KDevelop::ProjectFolderItem* f : folderList) { folders.append(f); } const auto targets = item->targetList(); for (KDevelop::ProjectTargetItem* f : targets) { const auto childs = f->fileList(); for (KDevelop::ProjectFileItem* child : childs) { d->urls.append(child->path().toUrl()); } } const auto files = item->fileList(); for (KDevelop::ProjectFileItem* f : files) { d->urls.append(f->path().toUrl()); } } auto win = ICore::self()->uiController()->activeMainWindow()->window(); - QMessageBox msgBox(QMessageBox::Question, i18n("Reformat files?"), + QMessageBox msgBox(QMessageBox::Question, i18nc("@title:window", "Reformat Files?"), i18n("Reformat all files in the selected folder?"), QMessageBox::Ok|QMessageBox::Cancel, win); msgBox.setDefaultButton(QMessageBox::Cancel); auto okButton = msgBox.button(QMessageBox::Ok); - okButton->setText(i18n("Reformat")); + okButton->setText(i18nc("@action:button", "Reformat")); msgBox.exec(); if (msgBox.clickedButton() == okButton) { auto formatterJob = new SourceFormatterJob(this); formatterJob->setFiles(d->urls); ICore::self()->runController()->registerJob(formatterJob); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { Q_D(SourceFormatterController); Q_UNUSED(parent); KDevelop::ContextMenuExtension ext; d->urls.clear(); d->prjItems.clear(); if (d->sourceFormatters.isEmpty()) { return ext; } if (context->hasType(KDevelop::Context::EditorContext)) { if (d->formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, d->formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { auto* filectx = static_cast(context); d->urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, d->formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { auto* prjctx = static_cast(context); d->prjItems = prjctx->items(); if (!d->prjItems.isEmpty()) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, d->formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForUrl(const QUrl& url, const QMimeType& mime) { const auto formatter = configForUrl(url).readEntry(mime.name(), QString()).split(QStringLiteral("||"), QString::SkipEmptyParts); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = globalConfig().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { Q_D(SourceFormatterController); d->enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { Q_D(SourceFormatterController); return d->enabled; } bool SourceFormatterController::hasFormatters() const { Q_D(const SourceFormatterController); return !d->sourceFormatters.isEmpty(); } QVector SourceFormatterController::formatters() const { Q_D(const SourceFormatterController); return d->sourceFormatters; } void SourceFormatterController::resetUi() { Q_D(SourceFormatterController); d->formatFilesAction->setEnabled(!d->sourceFormatters.isEmpty()); updateFormatTextAction(); } } diff --git a/kdevplatform/shell/sourceformatterselectionedit.cpp b/kdevplatform/shell/sourceformatterselectionedit.cpp index 00dc4f9f7a..e661c84483 100644 --- a/kdevplatform/shell/sourceformatterselectionedit.cpp +++ b/kdevplatform/shell/sourceformatterselectionedit.cpp @@ -1,613 +1,613 @@ /* This file is part of KDevelop * * Copyright (C) 2008 Cédric Pasteur * Copyright (C) 2017 Friedrich W. H. Kossebau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sourceformatterselectionedit.h" #include "ui_sourceformatterselectionedit.h" #include "sourceformattercontroller.h" #include "settings/editstyledialog.h" #include "debug.h" #include "core.h" #include "plugincontroller.h" #include #include #include // TODO: remove later #include #include #include #include #include #include #include #include #define STYLE_ROLE (Qt::UserRole+1) using namespace KDevelop; namespace { namespace Strings { QString userStylePrefix() { return QStringLiteral("User"); } } } struct LanguageSettings { QList mimetypes; QSet formatters; // weak pointers to selected formatter and style, no ownership KDevelop::SourceFormatter* selectedFormatter = nullptr; // Should never be zero KDevelop::SourceFormatterStyle* selectedStyle = nullptr; // TODO: can this be zero? Assume that not }; using LanguageMap = QMap; using FormatterMap = QMap; class KDevelop::SourceFormatterSelectionEditPrivate { public: Ui::SourceFormatterSelectionEdit ui; // Language name -> language settings LanguageMap languages; // formatter name -> formatter. Formatters owned by this FormatterMap formatters; KTextEditor::Document* document; KTextEditor::View* view; }; SourceFormatterSelectionEdit::SourceFormatterSelectionEdit(QWidget* parent) : QWidget(parent) , d_ptr(new SourceFormatterSelectionEditPrivate) { Q_D(SourceFormatterSelectionEdit); d->ui.setupUi(this); connect(d->ui.cbLanguages, QOverload::of(&KComboBox::currentIndexChanged), this, &SourceFormatterSelectionEdit::selectLanguage); connect(d->ui.cbFormatters, QOverload::of(&KComboBox::currentIndexChanged), this, &SourceFormatterSelectionEdit::selectFormatter); connect(d->ui.styleList, &QListWidget::currentRowChanged, this, &SourceFormatterSelectionEdit::selectStyle); connect(d->ui.btnDelStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::deleteStyle); connect(d->ui.btnNewStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::newStyle); connect(d->ui.btnEditStyle, &QPushButton::clicked, this, &SourceFormatterSelectionEdit::editStyle); connect(d->ui.styleList, &QListWidget::itemChanged, this, &SourceFormatterSelectionEdit::styleNameChanged); d->document = KTextEditor::Editor::instance()->createDocument(this); d->document->setReadWrite(false); d->view = d->document->createView(d->ui.textEditor); d->view->setStatusBarEnabled(false); auto *layout2 = new QVBoxLayout(d->ui.textEditor); layout2->setMargin(0); layout2->addWidget(d->view); d->ui.textEditor->setLayout(layout2); d->view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(d->view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); iface->setConfigValue(QStringLiteral("scrollbar-minimap"), false); } SourceFormatterController* controller = Core::self()->sourceFormatterControllerInternal(); connect(controller, &SourceFormatterController::formatterLoaded, this, &SourceFormatterSelectionEdit::addSourceFormatter); connect(controller, &SourceFormatterController::formatterUnloading, this, &SourceFormatterSelectionEdit::removeSourceFormatter); const auto& formatters = controller->formatters(); for (auto* formatter : formatters) { addSourceFormatter(formatter); } } SourceFormatterSelectionEdit::~SourceFormatterSelectionEdit() { Q_D(SourceFormatterSelectionEdit); qDeleteAll(d->formatters); } static void selectAvailableStyle(LanguageSettings& lang) { Q_ASSERT(!lang.selectedFormatter->styles.empty()); lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } void SourceFormatterSelectionEdit::addSourceFormatter(ISourceFormatter* ifmt) { Q_D(SourceFormatterSelectionEdit); qCDebug(SHELL) << "Adding source formatter:" << ifmt->name(); SourceFormatter* formatter; FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name()); if (iter == d->formatters.constEnd()) { formatter = Core::self()->sourceFormatterControllerInternal()->createFormatterForPlugin(ifmt); d->formatters[ifmt->name()] = formatter; } else { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "loading which was already seen before by SourceFormatterSelectionEdit"; return; } for (const SourceFormatterStyle* style : qAsConst(formatter->styles)) { const auto mimeTypes = style->mimeTypes(); for ( const SourceFormatterStyle::MimeHighlightPair& item : mimeTypes) { QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); if (!mime.isValid()) { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "supports unknown mimetype entry" << item.mimeType; continue; } QString languageName = item.highlightMode; LanguageSettings& l = d->languages[languageName]; l.mimetypes.append(mime); l.formatters.insert( formatter ); // init selection if needed if (!l.selectedFormatter) { l.selectedFormatter = formatter; selectAvailableStyle(l); } } } resetUi(); } void SourceFormatterSelectionEdit::removeSourceFormatter(ISourceFormatter* ifmt) { Q_D(SourceFormatterSelectionEdit); qCDebug(SHELL) << "Removing source formatter:" << ifmt->name(); auto iter = d->formatters.find(ifmt->name()); if (iter == d->formatters.end()) { qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "unloading which was not seen before by SourceFormatterSelectionEdit"; return; } d->formatters.erase(iter); auto formatter = iter.value(); auto languageIter = d->languages.begin(); while (languageIter != d->languages.end()) { LanguageSettings& l = languageIter.value(); l.formatters.remove(formatter); if (l.formatters.isEmpty()) { languageIter = d->languages.erase(languageIter); } else { // reset selected formatter if needed if (l.selectedFormatter == formatter) { l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } ++languageIter; } } delete formatter; resetUi(); } void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config) { Q_D(SourceFormatterSelectionEdit); for (auto& l : d->languages) { // Pick the first appropriate mimetype for this language const QList mimetypes = l.mimetypes; for (const QMimeType& mimetype : mimetypes) { QStringList formatterAndStyleName = config.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); FormatterMap::const_iterator formatterIter = d->formatters.constFind(formatterAndStyleName.first()); if (formatterIter == d->formatters.constEnd()) { qCDebug(SHELL) << "Reference to unknown formatter" << formatterAndStyleName.first(); Q_ASSERT(!l.formatters.empty()); // otherwise there should be no entry for 'name' l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } else { l.selectedFormatter = formatterIter.value(); SourceFormatter::StyleMap::const_iterator styleIter = l.selectedFormatter->styles.constFind(formatterAndStyleName.at( 1 )); if (styleIter == l.selectedFormatter->styles.constEnd()) { qCDebug(SHELL) << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } } if (!l.selectedFormatter) { Q_ASSERT(!l.formatters.empty()); l.selectedFormatter = *l.formatters.begin(); } if (!l.selectedStyle) { selectAvailableStyle(l); } } resetUi(); } void SourceFormatterSelectionEdit::resetUi() { Q_D(SourceFormatterSelectionEdit); qCDebug(SHELL) << "Resetting UI"; // Create a sorted list of the languages, preferring firstly active, then loaded languages, then others QList sortedLanguages; for (const auto& languages : {ICore::self()->languageController()->activeLanguages(), ICore::self()->languageController()->loadedLanguages()}) { for (const auto* language : languages) { const auto languageName = language->name(); if (d->languages.contains(languageName) && !sortedLanguages.contains(languageName)) { sortedLanguages.append(languageName); } } } for (auto it = d->languages.constBegin(); it != d->languages.constEnd(); ++it) { const auto& languageName = it.key(); if (!sortedLanguages.contains(languageName)) { sortedLanguages.append(languageName); } } bool b = blockSignals( true ); d->ui.cbLanguages->blockSignals(!b); d->ui.cbFormatters->blockSignals(!b); d->ui.styleList->blockSignals(!b); d->ui.cbLanguages->clear(); d->ui.cbFormatters->clear(); d->ui.styleList->clear(); for (const auto& language : sortedLanguages) { d->ui.cbLanguages->addItem(language); } if (d->ui.cbLanguages->count() == 0) { d->ui.cbLanguages->setEnabled(false); selectLanguage( -1 ); } else { d->ui.cbLanguages->setCurrentIndex(0); d->ui.cbLanguages->setEnabled(true); selectLanguage( 0 ); } updatePreview(); blockSignals( b ); d->ui.cbLanguages->blockSignals(b); d->ui.cbFormatters->blockSignals(b); d->ui.styleList->blockSignals(b); } void SourceFormatterSelectionEdit::saveSettings(KConfigGroup& config) const { Q_D(const SourceFormatterSelectionEdit); // store formatters globally KConfigGroup globalConfig = Core::self()->sourceFormatterControllerInternal()->globalConfig(); for (const SourceFormatter* fmt : qAsConst(d->formatters)) { KConfigGroup fmtgrp = globalConfig.group( fmt->formatter->name() ); // delete all styles so we don't leave any behind when all user styles are deleted const auto oldStyleGroups = fmtgrp.groupList(); for (const QString& subgrp : oldStyleGroups) { if( subgrp.startsWith( Strings::userStylePrefix() ) ) { fmtgrp.deleteGroup( subgrp ); } } for (const SourceFormatterStyle* style : fmt->styles) { if( style->name().startsWith( Strings::userStylePrefix() ) ) { KConfigGroup stylegrp = fmtgrp.group( style->name() ); stylegrp.writeEntry( SourceFormatterController::styleCaptionKey(), style->caption() ); stylegrp.writeEntry( SourceFormatterController::styleShowPreviewKey(), style->usePreview() ); stylegrp.writeEntry( SourceFormatterController::styleContentKey(), style->content() ); stylegrp.writeEntry( SourceFormatterController::styleMimeTypesKey(), style->mimeTypesVariant() ); stylegrp.writeEntry( SourceFormatterController::styleSampleKey(), style->overrideSample() ); } } } globalConfig.sync(); // store selected formatters in given language for (const auto& setting : qAsConst(d->languages)) { for(const auto& mime : setting.mimetypes) { const QString formatterId = setting.selectedFormatter->formatter->name() + QLatin1String("||") + setting.selectedStyle->name(); config.writeEntry(mime.name(), formatterId); } } } void SourceFormatterSelectionEdit::enableStyleButtons() { Q_D(SourceFormatterSelectionEdit); bool userEntry = d->ui.styleList->currentItem() && d->ui.styleList->currentItem()->data(STYLE_ROLE).toString().startsWith(Strings::userStylePrefix()); QString languageName = d->ui.cbLanguages->currentText(); QMap::const_iterator it = d->languages.constFind(languageName); bool hasEditWidget = false; if (it != d->languages.constEnd()) { const LanguageSettings& l = it.value(); Q_ASSERT(l.selectedFormatter); ISourceFormatter* fmt = l.selectedFormatter->formatter; hasEditWidget = ( fmt && QScopedPointer(fmt->editStyleWidget( l.mimetypes.first() )) ); } d->ui.btnDelStyle->setEnabled(userEntry); d->ui.btnEditStyle->setEnabled(userEntry && hasEditWidget); d->ui.btnNewStyle->setEnabled(d->ui.cbFormatters->currentIndex() >= 0 && hasEditWidget); } void SourceFormatterSelectionEdit::selectLanguage( int idx ) { Q_D(SourceFormatterSelectionEdit); d->ui.cbFormatters->clear(); if( idx < 0 ) { d->ui.cbFormatters->setEnabled(false); selectFormatter( -1 ); return; } d->ui.cbFormatters->setEnabled(true); { QSignalBlocker blocker(d->ui.cbFormatters); LanguageSettings& l = d->languages[d->ui.cbLanguages->itemText(idx)]; for (const SourceFormatter* fmt : qAsConst(l.formatters)) { d->ui.cbFormatters->addItem(fmt->formatter->caption(), fmt->formatter->name()); } d->ui.cbFormatters->setCurrentIndex(d->ui.cbFormatters->findData(l.selectedFormatter->formatter->name())); } selectFormatter(d->ui.cbFormatters->currentIndex()); emit changed(); } void SourceFormatterSelectionEdit::selectFormatter( int idx ) { Q_D(SourceFormatterSelectionEdit); d->ui.styleList->clear(); if( idx < 0 ) { d->ui.styleList->setEnabled(false); enableStyleButtons(); return; } d->ui.styleList->setEnabled(true); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; Q_ASSERT( idx < l.formatters.size() ); FormatterMap::const_iterator formatterIter = d->formatters.constFind(d->ui.cbFormatters->itemData(idx).toString()); Q_ASSERT( formatterIter != d->formatters.constEnd() ); Q_ASSERT( l.formatters.contains(formatterIter.value()) ); if (l.selectedFormatter != formatterIter.value()) { l.selectedFormatter = formatterIter.value(); l.selectedStyle = nullptr; // will hold 0 until a style is picked } for (const SourceFormatterStyle* style : qAsConst(formatterIter.value()->styles)) { if (!style->supportsLanguage(d->ui.cbLanguages->currentText())) { // do not list items which do not support the selected language continue; } QListWidgetItem* item = addStyle( *style ); if (style == l.selectedStyle) { d->ui.styleList->setCurrentItem(item); } } if (l.selectedStyle == nullptr) { d->ui.styleList->setCurrentRow(0); } enableStyleButtons(); emit changed(); } void SourceFormatterSelectionEdit::selectStyle( int row ) { Q_D(SourceFormatterSelectionEdit); if( row < 0 ) { enableStyleButtons(); return; } d->ui.styleList->setCurrentRow(row); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; l.selectedStyle = l.selectedFormatter->styles[d->ui.styleList->item(row)->data(STYLE_ROLE).toString()]; enableStyleButtons(); updatePreview(); emit changed(); } void SourceFormatterSelectionEdit::deleteStyle() { Q_D(SourceFormatterSelectionEdit); Q_ASSERT( d->ui.styleList->currentRow() >= 0 ); QListWidgetItem* item = d->ui.styleList->currentItem(); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatter::StyleMap::iterator styleIter = fmt->styles.find(item->data( STYLE_ROLE ).toString()); QStringList otherLanguageNames; QList otherlanguages; for (LanguageMap::iterator languageIter = d->languages.begin(); languageIter != d->languages.end(); ++languageIter) { if ( &languageIter.value() != &l && languageIter.value().selectedStyle == styleIter.value() ) { otherLanguageNames.append(languageIter.key()); otherlanguages.append(&languageIter.value()); } } if (!otherLanguageNames.empty() && KMessageBox::warningContinueCancel(this, i18n("The style %1 is also used for the following languages:\n%2.\nAre you sure you want to delete it?", - styleIter.value()->caption(), otherLanguageNames.join(QLatin1Char('\n'))), i18n("Style being deleted")) != KMessageBox::Continue) { + styleIter.value()->caption(), otherLanguageNames.join(QLatin1Char('\n'))), i18nc("@title:window", "Deleting Style")) != KMessageBox::Continue) { return; } d->ui.styleList->takeItem(d->ui.styleList->currentRow()); fmt->styles.erase(styleIter); delete item; selectStyle(d->ui.styleList->count() > 0 ? 0 : -1); for (LanguageSettings* lang : qAsConst(otherlanguages)) { selectAvailableStyle(*lang); } updatePreview(); emit changed(); } void SourceFormatterSelectionEdit::editStyle() { Q_D(SourceFormatterSelectionEdit); QString language = d->ui.cbLanguages->currentText(); Q_ASSERT( d->languages.contains(language) ); LanguageSettings& l = d->languages[language]; SourceFormatter* fmt = l.selectedFormatter; QMimeType mimetype = l.mimetypes.first(); if( QScopedPointer(fmt->formatter->editStyleWidget( mimetype )) ) { KDevelop::ScopedDialog dlg(fmt->formatter, mimetype, *l.selectedStyle, this); if( dlg->exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg->content()); } updatePreview(); emit changed(); } } void SourceFormatterSelectionEdit::newStyle() { Q_D(SourceFormatterSelectionEdit); QListWidgetItem* item = d->ui.styleList->currentItem(); LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; SourceFormatter* fmt = l.selectedFormatter; int idx = 0; for (int i = 0; i < d->ui.styleList->count(); ++i) { QString name = d->ui.styleList->item(i)->data(STYLE_ROLE).toString(); if( name.startsWith( Strings::userStylePrefix() ) && name.midRef( Strings::userStylePrefix().length() ).toInt() >= idx ) { idx = name.midRef( Strings::userStylePrefix().length() ).toInt(); } } // Increase number for next style idx++; auto* s = new SourceFormatterStyle(QStringLiteral("%1%2").arg(Strings::userStylePrefix()).arg(idx)); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); s->copyDataFrom( existstyle ); } else { s->setCaption( i18n( "New Style" ) ); } fmt->styles[ s->name() ] = s; QListWidgetItem* newitem = addStyle( *s ); selectStyle(d->ui.styleList->row(newitem)); d->ui.styleList->editItem(newitem); emit changed(); } void SourceFormatterSelectionEdit::styleNameChanged( QListWidgetItem* item ) { Q_D(SourceFormatterSelectionEdit); if ( !item->isSelected() ) { return; } LanguageSettings& l = d->languages[d->ui.cbLanguages->currentText()]; l.selectedStyle->setCaption( item->text() ); emit changed(); } QListWidgetItem* SourceFormatterSelectionEdit::addStyle( const SourceFormatterStyle& s ) { Q_D(SourceFormatterSelectionEdit); auto* item = new QListWidgetItem(d->ui.styleList); item->setText( s.caption() ); item->setData( STYLE_ROLE, s.name() ); if( s.name().startsWith( Strings::userStylePrefix() ) ) { item->setFlags( item->flags() | Qt::ItemIsEditable ); } d->ui.styleList->addItem(item); return item; } void SourceFormatterSelectionEdit::updatePreview() { Q_D(SourceFormatterSelectionEdit); d->document->setReadWrite(true); QString langName = d->ui.cbLanguages->itemText(d->ui.cbLanguages->currentIndex()); if( !langName.isEmpty() ) { LanguageSettings& l = d->languages[langName]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatterStyle* style = l.selectedStyle; d->ui.descriptionLabel->setText(style->description()); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; QMimeType mime = l.mimetypes.first(); d->document->setHighlightingMode(style->modeForMimetype(mime)); //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 KTextEditor::ConfigInterface* iface = qobject_cast(d->document); QVariant oldReplaceTabs; if (iface) { oldReplaceTabs = iface->configValue(QStringLiteral("replace-tabs")); iface->setConfigValue(QStringLiteral("replace-tabs"), false); } d->document->setText(ifmt->formatSourceWithStyle(*style, ifmt->previewText(*style, mime), QUrl(), mime)); if (iface) { iface->setConfigValue(QStringLiteral("replace-tabs"), oldReplaceTabs); } d->ui.previewLabel->show(); d->ui.textEditor->show(); }else{ d->ui.previewLabel->hide(); d->ui.textEditor->hide(); } } else { d->document->setText(i18n("No language selected")); } d->view->setCursorPosition(KTextEditor::Cursor(0, 0)); d->document->setReadWrite(false); } diff --git a/kdevplatform/shell/sourceformatterselectionedit.ui b/kdevplatform/shell/sourceformatterselectionedit.ui index a106cee5d4..60b5c96b32 100644 --- a/kdevplatform/shell/sourceformatterselectionedit.ui +++ b/kdevplatform/shell/sourceformatterselectionedit.ui @@ -1,181 +1,181 @@ KDevelop::SourceFormatterSelectionEdit 0 0 509 344 0 0 0 0 - Language: + Language: Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing 4 0 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical 20 10 - Preview: + Preview: 0 0 - Formatter: + Formatter: Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing 4 - Style: + Style: Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing 4 -1 - New + New - Edit... + Edit... - Delete + Delete QAbstractItemView::SelectRows false KComboBox QComboBox
KComboBox
diff --git a/kdevplatform/shell/uicontroller.cpp b/kdevplatform/shell/uicontroller.cpp index 9261d529f7..3b7c9b6023 100644 --- a/kdevplatform/shell/uicontroller.cpp +++ b/kdevplatform/shell/uicontroller.cpp @@ -1,802 +1,802 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "plugincontroller.h" #include "mainwindow.h" #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" #include "settings/analyzerspreferences.h" #include "settings/documentationpreferences.h" #include "settings/runtimespreferences.h" namespace KDevelop { class UiControllerPrivate { public: UiControllerPrivate(Core* core, UiController* controller) : core(core) , areasRestored(false) , m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ScratchpadView")] = Sublime::Left; - auto* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); + auto* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18nc("area", "Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; - a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); + a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18nc("area", "Debug")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("debug-run")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; - a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); + a = new Sublime::Area(m_controller, QStringLiteral("review"), i18nc("area", "Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("text-x-patch")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = nullptr; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { auto* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core* const core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: explicit UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = nullptr) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory* const m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: explicit ViewSelectorItem(const QString& text, IToolViewFactory* factory, QListWidget* parent = nullptr, int type = Type) : QListWidgetItem(text, parent, type) , factory(factory) {} IToolViewFactory* const factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: explicit NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(const QModelIndex& index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the tool view can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow* const m_mw; }; UiController::UiController(Core *core) : Sublime::Controller(nullptr), IUiController() , d_ptr(new UiControllerPrivate(core, this)) { setObjectName(QStringLiteral("UiController")); if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [this] (QWidget* old, QWidget* now) { Q_D(UiController); d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() = default; void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } auto *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) const auto clients = oldMain->guiFactory()->clients(); for (KXMLGUIClient *client : clients) { main->guiFactory()->addClient(client); } #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { Q_D(UiController); if(!d->areasRestored || !activeArea()) return nullptr; const QList views = activeArea()->toolViews(); for (Sublime::View* view : views) { auto* doc = qobject_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = nullptr; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { Q_D(UiController); if(!d->areasRestored) return; const QList views = activeArea()->toolViews(); for (Sublime::View* view : views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { Q_D(UiController); if (!factory) return; qCDebug(SHELL) ; auto *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { const auto areas = allAreas(); for (Sublime::Area* area : areas) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { const auto areas = allAreas(); for (Sublime::Area* area : areas) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); for (Sublime::View* toolView : qAsConst(area->toolViews())) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { Q_D(UiController); if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { Q_D(UiController); if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted for (Sublime::View* view : doc->views()) { const auto areas = allAreas(); for (Sublime::Area *area : areas) { if (area->removeToolView(view)) view->deleteLater(); } } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return nullptr; } Sublime::MainWindow *UiController::activeSublimeWindow() { Q_D(UiController); return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { Q_D(UiController); return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { for (Sublime::MainWindow* w : mainWindows()) { w->saveSettings(); } saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { Q_D(UiController); if (!mw || !mw->area()) return; ScopedDialog dia(mw); - dia->setWindowTitle(i18n("Select Tool View to Add")); + dia->setWindowTitle(i18nc("@title:window", "Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); auto *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { auto* item = new ViewSelectorItem(it.value()->title(), it.key(), list); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the tool view is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia.data(), &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { const auto items = list->selectedItems(); for (QListWidgetItem* item : items) { addNewToolView(mw, item); } } } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { Q_D(UiController); auto *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { ConfigDialog cfgDlg(activeMainWindow()); auto editorConfigPage = new EditorConfigPage(&cfgDlg); auto languageConfigPage = new LanguagePreferences(&cfgDlg); auto analyzersPreferences = new AnalyzersPreferences(&cfgDlg); auto documentationPreferences = new DocumentationPreferences(&cfgDlg); auto runtimesPreferences = new RuntimesPreferences(&cfgDlg); auto templateConfig = new TemplateConfig(&cfgDlg); const auto configPages = QVector { new UiPreferences(&cfgDlg), new PluginPreferences(&cfgDlg), new SourceFormatterSettings(&cfgDlg), new ProjectPreferences(&cfgDlg), new EnvironmentPreferences(QString(), &cfgDlg), templateConfig, documentationPreferences, analyzersPreferences, runtimesPreferences, languageConfigPage, editorConfigPage }; for (auto page : configPages) { cfgDlg.appendConfigPage(page); } cfgDlg.appendSubConfigPage(languageConfigPage, new BGPreferences(&cfgDlg)); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if (!page) continue; if (page->configPageType() == ConfigPage::LanguageConfigPage) { cfgDlg.appendSubConfigPage(languageConfigPage, page); } else if (page->configPageType() == ConfigPage::AnalyzerConfigPage) { cfgDlg.appendSubConfigPage(analyzersPreferences, page); } else if (page->configPageType() == ConfigPage::RuntimeConfigPage) { cfgDlg.appendSubConfigPage(runtimesPreferences, page); } else if (page->configPageType() == ConfigPage::DocumentationConfigPage) { cfgDlg.appendSubConfigPage(documentationPreferences, page); } else { cfgDlg.insertConfigPage(editorConfigPage, page); } } }; auto plugins = ICore::self()->pluginController()->loadedPlugins(); std::sort(plugins.begin(), plugins.end()); for (IPlugin* plugin : qAsConst(plugins)) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->workingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->workingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(const KSharedConfigPtr& config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); for (Sublime::Area* defaultArea : defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, QLatin1String("Area ") + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(const KSharedConfigPtr& config) { Q_D(UiController); KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); /* Offer all tool views to the default areas. */ for (Sublime::Area* area : defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ for (Sublime::Area* defaultArea : defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, QLatin1String("Area ") + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which tool views the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { Q_D(UiController); addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { for (Sublime::View *view : doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, QOverload::of(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; auto* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; auto* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } void UiController::postMessage(Sublime::Message* message) { // if Core has flag Core::NoUi there also is no window, so caught as well here Sublime::MainWindow* window = activeSublimeWindow(); if (!window) { delete message; return; } QMetaObject::invokeMethod(window, "postMessage", Q_ARG(Sublime::Message*, message)); } const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { Q_D(const UiController); return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { Q_D(const UiController); return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/kdevplatform/shell/workingsets/workingsettooltipwidget.cpp b/kdevplatform/shell/workingsets/workingsettooltipwidget.cpp index 93ecde3a5b..d2dea0fc52 100644 --- a/kdevplatform/shell/workingsets/workingsettooltipwidget.cpp +++ b/kdevplatform/shell/workingsets/workingsettooltipwidget.cpp @@ -1,393 +1,393 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "workingsettooltipwidget.h" #include #include #include #include "debug.h" #include "core.h" #include "documentcontroller.h" #include "mainwindow.h" #include #include #include #include #include #include #include #include "workingset.h" #include "workingsetcontroller.h" #include "workingsetfilelabel.h" #include "workingsettoolbutton.h" #include "workingsethelpers.h" using namespace KDevelop; class FileWidget : public QWidget { Q_OBJECT public: QToolButton* m_button; class WorkingSetFileLabel* m_label; }; WorkingSetToolTipWidget::WorkingSetToolTipWidget(QWidget* parent, WorkingSet* set, MainWindow* mainwindow) : QWidget(parent), m_set(set) { auto* layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setMargin(0); connect(mainwindow->area(), &Sublime::Area::viewAdded, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(mainwindow->area(), &Sublime::Area::viewRemoved, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); connect(Core::self()->workingSetControllerInternal(), &WorkingSetController::workingSetSwitched, this, &WorkingSetToolTipWidget::updateFileButtons); // title bar { auto* topLayout = new QHBoxLayout; m_setButton = new WorkingSetToolButton(this, set); m_setButton->hide(); topLayout->addSpacing(5); auto* icon = new QLabel; topLayout->addWidget(icon); topLayout->addSpacing(5); QString label; if (m_set->isConnected(mainwindow->area())) { label = i18n("Active Working Set"); } else { label = i18n("Working Set"); } auto* name = new QLabel(label); name->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); topLayout->addWidget(name); topLayout->addSpacing(10); icon->setPixmap(m_setButton->icon().pixmap(name->sizeHint().height()+8, name->sizeHint().height()+8)); topLayout->addStretch(); m_openButton = new QPushButton; m_openButton->setFlat(true); topLayout->addWidget(m_openButton); m_deleteButton = new QPushButton; m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); - m_deleteButton->setText(i18n("Delete")); - m_deleteButton->setToolTip(i18n("Remove this working set. The contained documents are not affected.")); + m_deleteButton->setText(i18nc("@action:button", "Delete")); + m_deleteButton->setToolTip(i18nc("@info:tooltip", "Remove this working set. The contained documents are not affected.")); m_deleteButton->setFlat(true); connect(m_deleteButton, &QPushButton::clicked, m_set, [&] { m_set->deleteSet(false); }); connect(m_deleteButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); topLayout->addWidget(m_deleteButton); layout->addLayout(topLayout); // horizontal line auto* line = new QFrame(); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Raised); layout->addWidget(line); } // everything else is added to the following widget which just has a different background color auto* bodyLayout = new QVBoxLayout; { auto* body = new QWidget(); body->setLayout(bodyLayout); layout->addWidget(body); body->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); } // document list actions { auto* actionsLayout = new QHBoxLayout; - m_documentsLabel = new QLabel(i18n("Documents:")); + m_documentsLabel = new QLabel(i18nc("@label", "Documents:")); m_documentsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); actionsLayout->addWidget(m_documentsLabel); actionsLayout->addStretch(); m_mergeButton = new QPushButton; m_mergeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); - m_mergeButton->setText(i18n("Add All")); - m_mergeButton->setToolTip(i18n("Add all documents that are part of this working set to the currently active working set.")); + m_mergeButton->setText(i18nc("@action:button", "Add All")); + m_mergeButton->setToolTip(i18nc("@info:tooltip", "Add all documents that are part of this working set to the currently active working set")); m_mergeButton->setFlat(true); connect(m_mergeButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::mergeSet); actionsLayout->addWidget(m_mergeButton); m_subtractButton = new QPushButton; m_subtractButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); - m_subtractButton->setText(i18n("Remove All")); - m_subtractButton->setToolTip(i18n("Remove all documents that are part of this working set from the currently active working set.")); + m_subtractButton->setText(i18nc("@action:button", "Remove All")); + m_subtractButton->setToolTip(i18nc("@info:tooltip", "Remove all documents that are part of this working set from the currently active working set")); m_subtractButton->setFlat(true); connect(m_subtractButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::subtractSet); actionsLayout->addWidget(m_subtractButton); bodyLayout->addLayout(actionsLayout); } QSet hadFiles; auto* filesLayout = new QVBoxLayout; filesLayout->setMargin(0); const auto setFiles = m_set->fileList(); for (const QString& file : setFiles) { if(hadFiles.contains(file)) continue; hadFiles.insert(file); auto* widget = new FileWidget; widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); auto* fileLayout = new QHBoxLayout(widget); auto* plusButton = new QToolButton; plusButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); fileLayout->addWidget(plusButton); auto* fileLabel = new WorkingSetFileLabel; fileLabel->setTextFormat(Qt::RichText); // We add spaces behind and after, to make it look nicer fileLabel->setText(QLatin1String(" ") + Core::self()->projectController()->prettyFileName(QUrl::fromUserInput(file)) + QLatin1String(" ")); fileLabel->setToolTip(i18nc("@info:tooltip", "Click to open and activate this document.")); fileLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); fileLayout->addWidget(fileLabel); fileLayout->setMargin(0); plusButton->setMaximumHeight(fileLabel->sizeHint().height() + 4); plusButton->setMaximumWidth(plusButton->maximumHeight()); plusButton->setObjectName(file); fileLabel->setObjectName(file); fileLabel->setCursor(QCursor(Qt::PointingHandCursor)); widget->m_button = plusButton; widget->m_label = fileLabel; filesLayout->addWidget(widget); m_fileWidgets.insert(file, widget); m_orderedFileWidgets.push_back(widget); connect(plusButton, &QToolButton::clicked, this, &WorkingSetToolTipWidget::buttonClicked); connect(fileLabel, &WorkingSetFileLabel::clicked, this, &WorkingSetToolTipWidget::labelClicked); } bodyLayout->addLayout(filesLayout); updateFileButtons(); connect(set, &WorkingSet::setChangedSignificantly, this, &WorkingSetToolTipWidget::updateFileButtons); connect(mainwindow->area(), &Sublime::Area::changedWorkingSet, this, &WorkingSetToolTipWidget::updateFileButtons, Qt::QueuedConnection); QMetaObject::invokeMethod(this, "updateFileButtons"); } void WorkingSetToolTipWidget::nextDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qCWarning(SHELL) << "Found no active document"; return; } int next = (active + 1) % m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) next = (next + 1) % m_orderedFileWidgets.size(); m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::previousDocument() { int active = -1; for(int a = 0; a < m_orderedFileWidgets.size(); ++a) if(m_orderedFileWidgets[a]->m_label->isActive()) active = a; if(active == -1) { qCWarning(SHELL) << "Found no active document"; return; } int next = active - 1; if(next < 0) next += m_orderedFileWidgets.size(); while(m_orderedFileWidgets[next]->isHidden() && next != active) { next -= 1; if(next < 0) next += m_orderedFileWidgets.size(); } m_orderedFileWidgets[next]->m_label->emitClicked(); } void WorkingSetToolTipWidget::updateFileButtons() { auto* mainWindow = qobject_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); WorkingSetController* controller = Core::self()->workingSetControllerInternal(); ActiveToolTip* tooltip = controller->tooltip(); QString activeFile; if(mainWindow->area()->activeView()) activeFile = mainWindow->area()->activeView()->document()->documentSpecifier(); WorkingSet* currentWorkingSet = nullptr; QSet openFiles; if(!mainWindow->area()->workingSet().isEmpty()) { currentWorkingSet = controller->workingSet(mainWindow->area()->workingSet()); openFiles = currentWorkingSet->fileSet(); } bool allOpen = true; bool noneOpen = true; bool needResize = false; bool allHidden = true; for(QMap< QString, FileWidget* >::iterator it = m_fileWidgets.begin(); it != m_fileWidgets.end(); ++it) { if(openFiles.contains(it.key())) { noneOpen = false; - (*it)->m_button->setToolTip(i18n("Remove this file from the current working set")); + (*it)->m_button->setToolTip(i18nc("@info:tooltip", "Remove this file from the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); (*it)->show(); }else{ allOpen = false; - (*it)->m_button->setToolTip(i18n("Add this file to the current working set")); + (*it)->m_button->setToolTip(i18nc("@info:tooltip", "Add this file to the current working set")); (*it)->m_button->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); if(currentWorkingSet == m_set) { (*it)->hide(); needResize = true; } } if(!(*it)->isHidden()) allHidden = false; (*it)->m_label->setIsActiveFile(it.key() == activeFile); } // NOTE: always hide merge&subtract all on current working set // if we want to enable mergeButton, we have to fix it's behavior since it operates directly on the // set contents and not on the m_fileWidgets m_mergeButton->setHidden(allOpen || currentWorkingSet == m_set); m_subtractButton->setHidden(noneOpen || currentWorkingSet == m_set); m_deleteButton->setHidden(m_set->hasConnectedAreas()); m_documentsLabel->setHidden(m_mergeButton->isHidden() && m_subtractButton->isHidden() && m_deleteButton->isHidden()); if(currentWorkingSet == m_set) { disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-development-close"))); - m_openButton->setText(i18n("Stash")); + m_openButton->setText(i18nc("@action:button", "Stash")); }else{ disconnect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::closeSet); connect(m_openButton, &QPushButton::clicked, m_setButton, &WorkingSetToolButton::loadSet); disconnect(m_openButton, &QPushButton::clicked, this, &WorkingSetToolTipWidget::shouldClose); m_openButton->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); - m_openButton->setText(i18n("Load")); + m_openButton->setText(i18nc("@action:button", "Load")); } if(allHidden && tooltip) tooltip->hide(); if(needResize && tooltip) tooltip->resize(tooltip->sizeHint()); } void WorkingSetToolTipWidget::buttonClicked(bool) { QPointer stillExists(this); auto* s = qobject_cast(sender()); Q_ASSERT(s); auto* mainWindow = qobject_cast(Core::self()->uiController()->activeMainWindow()); Q_ASSERT(mainWindow); QSet openFiles = Core::self()->workingSetControllerInternal()->workingSet(mainWindow->area()->workingSet())->fileSet(); if(!openFiles.contains(s->objectName())) { Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); }else{ openFiles.remove(s->objectName()); filterViews(openFiles); } if(stillExists) updateFileButtons(); } void WorkingSetToolTipWidget::labelClicked() { QPointer stillExists(this); auto* s = qobject_cast(sender()); Q_ASSERT(s); bool found = false; auto* window = static_cast(ICore::self()->uiController()->activeMainWindow()); const auto views = window->area()->views(); for (Sublime::View* view : views) { if(view->document()->documentSpecifier() == s->objectName()) { window->activateView(view); found = true; break; } } if(!found) Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(s->objectName())); if(stillExists) updateFileButtons(); } #include "workingsettooltipwidget.moc"