File indexing completed on 2024-04-28 04:37:24

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "runcontroller.h"
0009 
0010 #include <QDBusConnection>
0011 #include <QPalette>
0012 
0013 #include <KAboutData>
0014 #include <KActionCollection>
0015 #include <KActionMenu>
0016 #include <KDialogJobUiDelegate>
0017 #include <KLocalizedString>
0018 #include <KSelectAction>
0019 
0020 #include <interfaces/iproject.h>
0021 #include <interfaces/idocumentcontroller.h>
0022 #include <interfaces/ilauncher.h>
0023 #include <interfaces/ilaunchmode.h>
0024 #include <interfaces/launchconfigurationtype.h>
0025 #include <outputview/outputjob.h>
0026 #include <project/projectmodel.h>
0027 #include <sublime/message.h>
0028 
0029 #include "core.h"
0030 #include "uicontroller.h"
0031 #include "projectcontroller.h"
0032 #include "mainwindow.h"
0033 #include "launchconfiguration.h"
0034 #include "launchconfigurationdialog.h"
0035 #include "unitylauncher.h"
0036 #include "debug.h"
0037 #include <interfaces/isession.h>
0038 
0039 #include <interfaces/contextmenuextension.h>
0040 #include <interfaces/context.h>
0041 #include <sublime/area.h>
0042 
0043 using namespace KDevelop;
0044 
0045 namespace {
0046 namespace Strings {
0047 QString LaunchConfigurationsGroup()
0048 {
0049     return QStringLiteral("Launch");
0050 }
0051 
0052 QString LaunchConfigurationsListEntry()
0053 {
0054     return QStringLiteral("Launch Configurations");
0055 }
0056 
0057 QString CurrentLaunchConfigProjectEntry()
0058 {
0059     return QStringLiteral("Current Launch Config Project");
0060 }
0061 
0062 QString CurrentLaunchConfigNameEntry()
0063 {
0064     return QStringLiteral("Current Launch Config GroupName");
0065 }
0066 
0067 QString ConfiguredFromProjectItemEntry()
0068 {
0069     return QStringLiteral("Configured from ProjectItem");
0070 }
0071 }
0072 }
0073 
0074 using Target = QPair<QString, IProject*>;
0075 Q_DECLARE_METATYPE(Target)
0076 
0077 
0078 //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs
0079 //TODO: Doesn't auto-select launch configs opened from projects
0080 
0081 class DebugMode : public ILaunchMode
0082 {
0083 public:
0084     DebugMode() {}
0085     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); }
0086     QString id() const override { return QStringLiteral("debug"); }
0087     QString name() const override { return i18nc("launch mode", "Debug"); }
0088 };
0089 
0090 class ProfileMode : public ILaunchMode
0091 {
0092 public:
0093     ProfileMode() {}
0094     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); }
0095     QString id() const override { return QStringLiteral("profile"); }
0096     QString name() const override { return i18nc("launch mode", "Profile"); }
0097 };
0098 
0099 class ExecuteMode : public ILaunchMode
0100 {
0101 public:
0102     ExecuteMode() {}
0103     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); }
0104     QString id() const override { return QStringLiteral("execute"); }
0105     QString name() const override { return i18nc("launch mode", "Execute"); }
0106 };
0107 
0108 class KDevelop::RunControllerPrivate
0109 {
0110 public:
0111     QItemDelegate* delegate;
0112 
0113     IRunController::State state;
0114 
0115     RunController* q;
0116 
0117     QHash<KJob*, QAction*> jobs;
0118     QAction* stopAction;
0119     KActionMenu* stopJobsMenu;
0120     QAction* runAction;
0121     QAction* dbgAction;
0122     KSelectAction* currentTargetAction;
0123     QMap<QString,LaunchConfigurationType*> launchConfigurationTypes;
0124     QList<LaunchConfiguration*> launchConfigurations;
0125     QMap<QString,ILaunchMode*> launchModes;
0126     QMap<int,QPair<QString,QString> > launchAsInfo;
0127     KDevelop::ProjectBaseItem* contextItem;
0128     DebugMode* debugMode;
0129     ExecuteMode* executeMode;
0130     ProfileMode* profileMode;
0131     UnityLauncher* unityLauncher;
0132 
0133     bool hasLaunchConfigType( const QString& typeId )
0134     {
0135         return launchConfigurationTypes.contains( typeId );
0136     }
0137     void saveCurrentLaunchAction()
0138     {
0139         if (!currentTargetAction) return;
0140 
0141         if( currentTargetAction->currentAction() )
0142         {
0143             KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
0144             LaunchConfiguration* l = static_cast<LaunchConfiguration*>( currentTargetAction->currentAction()->data().value<void*>() );
0145             grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() );
0146             grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() );
0147             grp.sync();
0148         }
0149     }
0150 
0151     QString launchActionText( LaunchConfiguration* l )
0152     {
0153         QString label;
0154         if( l->project() )
0155         {
0156             label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name());
0157         } else
0158         {
0159             label = l->name();
0160         }
0161         return label;
0162     }
0163 
0164     void launchAs( int id )
0165     {
0166         //qCDebug(SHELL) << "Launching id:" << id;
0167         QPair<QString,QString> info = launchAsInfo[id];
0168         //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second;
0169         LaunchConfigurationType* type = launchConfigurationTypeForId( info.first );
0170         ILaunchMode* mode = q->launchModeForId( info.second );
0171 
0172         //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id();
0173         if( type && mode )
0174         {
0175             const auto launchers = type->launchers();
0176             auto it = std::find_if(launchers.begin(), launchers.end(), [&](ILauncher* l) {
0177                 //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes();
0178                 return (l->supportedModes().contains(mode->id()));
0179             });
0180             if (it != launchers.end()) {
0181                 ILauncher* launcher = *it;
0182 
0183                 QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index());
0184                 auto it = std::find_if(launchConfigurations.constBegin(), launchConfigurations.constEnd(),
0185                                        [&] (LaunchConfiguration* l) {
0186                     QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList());
0187                     if (l->type() == type && path == itemPath) {
0188                         qCDebug(SHELL) << "already generated ilaunch" << path;
0189                         return true;
0190                     }
0191                     return false;
0192                 });
0193                 ILaunchConfiguration* ilaunch = (it != launchConfigurations.constEnd()) ? *it : nullptr;
0194 
0195                 if (!ilaunch) {
0196                     ilaunch = q->createLaunchConfiguration( type,
0197                                                             qMakePair( mode->id(), launcher->id() ),
0198                                                             contextItem->project(),
0199                                                             contextItem->text() );
0200                     auto* launch = static_cast<LaunchConfiguration*>(ilaunch);
0201                     type->configureLaunchFromItem( launch->config(), contextItem );
0202                     launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath);
0203                     //qCDebug(SHELL) << "created config, launching";
0204                 } else {
0205                     //qCDebug(SHELL) << "reusing generated config, launching";
0206                 }
0207                 q->setDefaultLaunch(ilaunch);
0208                 q->execute( mode->id(), ilaunch );
0209             }
0210         }
0211     }
0212 
0213     void updateCurrentLaunchAction()
0214     {
0215         if (!currentTargetAction) return;
0216 
0217         KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
0218         QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" );
0219         QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" );
0220 
0221         LaunchConfiguration* l = nullptr;
0222         if( currentTargetAction->currentAction() )
0223         {
0224             l = static_cast<LaunchConfiguration*>( currentTargetAction->currentAction()->data().value<void*>() );
0225         } else if( !launchConfigurations.isEmpty() )
0226         {
0227             l = launchConfigurations.at( 0 );
0228         }
0229 
0230         if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) )
0231         {
0232             const auto actions = currentTargetAction->actions();
0233             for (QAction* a : actions) {
0234                 LaunchConfiguration* l = static_cast<LaunchConfiguration*>( qvariant_cast<void*>( a->data() ) );
0235                 if( currentLaunchName == l->configGroupName()
0236                     && ( ( currentLaunchProject.isEmpty() && !l->project() )
0237                          || ( l->project() && l->project()->name() == currentLaunchProject ) ) )
0238                 {
0239                     a->setChecked( true );
0240                     break;
0241                 }
0242             }
0243         }
0244         if( !currentTargetAction->currentAction() )
0245         {
0246             qCDebug(SHELL) << "oops no current action, using first if list is non-empty";
0247             if( !currentTargetAction->actions().isEmpty() )
0248             {
0249                 currentTargetAction->actions().at(0)->setChecked( true );
0250             }
0251         }
0252     }
0253 
0254     void addLaunchAction( LaunchConfiguration* l )
0255     {
0256         if (!currentTargetAction) return;
0257 
0258         QAction* action = currentTargetAction->addAction(launchActionText( l ));
0259         action->setData(QVariant::fromValue<void*>(l));
0260     }
0261     void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj )
0262     {
0263         KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup());
0264         const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList());
0265 
0266         for (const QString& cfg : configs) {
0267             KConfigGroup grp = group.group( cfg );
0268             if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) )
0269             {
0270                 q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) );
0271             }
0272         }
0273     }
0274     LaunchConfigurationType* launchConfigurationTypeForId( const QString& id )
0275     {
0276         QMap<QString, LaunchConfigurationType*>::iterator it = launchConfigurationTypes.find( id );
0277         if( it != launchConfigurationTypes.end() )
0278         {
0279             return it.value();
0280         } else
0281         {
0282             qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys();
0283         }
0284         return nullptr;
0285 
0286     }
0287 
0288 };
0289 
0290 RunController::RunController(QObject *parent)
0291     : IRunController(parent)
0292     , d_ptr(new RunControllerPrivate)
0293 {
0294     Q_D(RunController);
0295 
0296     setObjectName(QStringLiteral("RunController"));
0297 
0298     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"),
0299         this, QDBusConnection::ExportScriptableSlots);
0300 
0301     // TODO: need to implement compile only if needed before execute
0302     // TODO: need to implement abort all running programs when project closed
0303 
0304     d->currentTargetAction = nullptr;
0305     d->state = Idle;
0306     d->q = this;
0307     d->delegate = new RunDelegate(this);
0308     d->contextItem = nullptr;
0309     d->executeMode = nullptr;
0310     d->debugMode = nullptr;
0311     d->profileMode = nullptr;
0312 
0313     d->unityLauncher = new UnityLauncher(this);
0314     d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName());
0315 
0316     if(!(Core::self()->setupFlags() & Core::NoUi)) {
0317         // Note that things like registerJob() do not work without the actions, it'll simply crash.
0318         setupActions();
0319     }
0320 }
0321 
0322 RunController::~RunController() = default;
0323 
0324 void KDevelop::RunController::launchChanged( LaunchConfiguration* l )
0325 {
0326     Q_D(RunController);
0327 
0328     const auto actions = d->currentTargetAction->actions();
0329     for (QAction* a : actions) {
0330         if( static_cast<LaunchConfiguration*>( a->data().value<void*>() ) == l )
0331         {
0332             a->setText( d->launchActionText( l ) );
0333             break;
0334         }
0335     }
0336 }
0337 
0338 void RunController::cleanup()
0339 {
0340     Q_D(RunController);
0341 
0342     delete d->executeMode;
0343     d->executeMode = nullptr;
0344     delete d->profileMode;
0345     d->profileMode = nullptr;
0346     delete d->debugMode;
0347     d->debugMode = nullptr;
0348 
0349     stopAllProcesses();
0350     d->saveCurrentLaunchAction();
0351 }
0352 
0353 void RunController::initialize()
0354 {
0355     Q_D(RunController);
0356 
0357     d->executeMode = new ExecuteMode();
0358     addLaunchMode( d->executeMode );
0359     d->profileMode = new ProfileMode();
0360     addLaunchMode( d->profileMode );
0361     d->debugMode = new DebugMode;
0362     addLaunchMode( d->debugMode );
0363     d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr );
0364 
0365     const auto projects = Core::self()->projectController()->projects();
0366     for (IProject* project : projects) {
0367         slotProjectOpened(project);
0368     }
0369     connect(Core::self()->projectController(), &IProjectController::projectOpened,
0370             this, &RunController::slotProjectOpened);
0371     connect(Core::self()->projectController(), &IProjectController::projectClosing,
0372             this, &RunController::slotProjectClosing);
0373 
0374     if( (Core::self()->setupFlags() & Core::NoUi) == 0 )
0375     {
0376         // Only do this in GUI mode
0377         d->updateCurrentLaunchAction();
0378     }
0379 }
0380 
0381 KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch)
0382 {
0383     if( !launch )
0384     {
0385         qCDebug(SHELL) << "execute called without launch config!";
0386         return nullptr;
0387     }
0388     auto* run = static_cast<LaunchConfiguration*>(launch);
0389     //TODO: Port to launch framework, probably needs to be part of the launcher
0390     //if(!run.dependencies().isEmpty())
0391     //    ICore::self()->documentController()->saveAllDocuments(IDocument::Silent);
0392 
0393     //foreach(KJob* job, run.dependencies())
0394     //{
0395     //    jobs.append(job);
0396     //}
0397 
0398     qCDebug(SHELL) << "mode:" << runMode;
0399     QString launcherId = run->launcherForMode( runMode );
0400     qCDebug(SHELL) << "launcher id:" << launcherId;
0401 
0402     ILauncher* launcher = run->type()->launcherForId( launcherId );
0403 
0404     if( !launcher ) {
0405         const QString messageText = i18n("The current launch configuration does not support the '%1' mode.", runMode);
0406         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
0407         ICore::self()->uiController()->postMessage(message);
0408         return nullptr;
0409     }
0410 
0411     KJob* launchJob = launcher->start(runMode, run);
0412     registerJob(launchJob);
0413     return launchJob;
0414 }
0415 
0416 void RunController::setupActions()
0417 {
0418     Q_D(RunController);
0419 
0420     QAction* action;
0421 
0422     // TODO not multi-window friendly, FIXME
0423     KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection();
0424 
0425     action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action", "Configure Launches..."), this);
0426     ac->addAction(QStringLiteral("configure_launches"), action);
0427     action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item
0428     action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog"));
0429     action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones."));
0430     connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog);
0431 
0432     d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18nc("@action", "Execute Launch"), this);
0433     d->runAction->setIconText( i18nc("@action Short text for 'Execute Launch' used in the toolbar", "Execute") );
0434     ac->setDefaultShortcut(d->runAction, Qt::SHIFT | Qt::Key_F9);
0435     d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch"));
0436     d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration."));
0437     ac->addAction(QStringLiteral("run_execute"), d->runAction);
0438     connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute);
0439 
0440     d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18nc("@action", "Debug Launch"), this);
0441     ac->setDefaultShortcut(d->dbgAction, Qt::ALT | Qt::Key_F9);
0442     d->dbgAction->setIconText( i18nc("@action Short text for 'Debug Launch' used in the toolbar", "Debug") );
0443     d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch"));
0444     d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger."));
0445     ac->addAction(QStringLiteral("run_debug"), d->dbgAction);
0446     connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug);
0447     Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction);
0448 
0449 //     TODO: at least get a profile target, it's sad to have the menu entry without a profiler
0450 //     QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this);
0451 //     profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch"));
0452 //     profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler."));
0453 //     ac->addAction("run_profile", profileAction);
0454 //     connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile()));
0455 
0456     action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop All Jobs"), this);
0457     action->setIconText(i18nc("@action Short text for 'Stop All Jobs' used in the toolbar", "Stop All"));
0458     // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut
0459     ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape")));
0460     action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs"));
0461     action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped."));
0462     action->setEnabled(false);
0463     ac->addAction(QStringLiteral("run_stop_all"), action);
0464     connect(action, &QAction::triggered, this, &RunController::stopAllProcesses);
0465     Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action);
0466 
0467     action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop"), this);
0468     d->stopJobsMenu->setPopupMode(QToolButton::InstantPopup);
0469     action->setIconText(i18nc("@action Short text for 'Stop' used in the toolbar", "Stop"));
0470     action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs"));
0471     action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually."));
0472     action->setEnabled(false);
0473     ac->addAction(QStringLiteral("run_stop_menu"), action);
0474 
0475     d->currentTargetAction = new KSelectAction( i18nc("@title:menu", "Current Launch Configuration"), this);
0476     d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration"));
0477     d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked."));
0478     connect(d->currentTargetAction, qOverload<QAction*>(&KSelectAction::triggered), this, [d] { d->saveCurrentLaunchAction(); });
0479     ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction);
0480 }
0481 
0482 LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id )
0483 {
0484     Q_D(RunController);
0485 
0486     return d->launchConfigurationTypeForId( id );
0487 }
0488 
0489 void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project)
0490 {
0491     Q_D(RunController);
0492 
0493     d->readLaunchConfigs( project->projectConfiguration(), project );
0494     d->updateCurrentLaunchAction();
0495 }
0496 
0497 void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project)
0498 {
0499     Q_D(RunController);
0500 
0501     if (!d->currentTargetAction) return;
0502 
0503     const auto actions = d->currentTargetAction->actions();
0504     for (QAction* action : actions) {
0505         LaunchConfiguration* l = static_cast<LaunchConfiguration*>(qvariant_cast<void*>(action->data()));
0506         if ( project == l->project() ) {
0507             l->save();
0508             d->launchConfigurations.removeAll(l);
0509             delete l;
0510             bool wasSelected = action->isChecked();
0511             delete action;
0512             if (wasSelected && !d->currentTargetAction->actions().isEmpty())
0513                 d->currentTargetAction->actions().at(0)->setChecked(true);
0514         }
0515     }
0516 }
0517 
0518 void RunController::slotDebug()
0519 {
0520     Q_D(RunController);
0521 
0522     if (d->launchConfigurations.isEmpty()) {
0523         showConfigurationDialog();
0524     }
0525 
0526     if (!d->launchConfigurations.isEmpty()) {
0527         executeDefaultLaunch( QStringLiteral("debug") );
0528     }
0529 }
0530 
0531 void RunController::slotProfile()
0532 {
0533     Q_D(RunController);
0534 
0535     if (d->launchConfigurations.isEmpty()) {
0536         showConfigurationDialog();
0537     }
0538 
0539     if (!d->launchConfigurations.isEmpty()) {
0540         executeDefaultLaunch( QStringLiteral("profile") );
0541     }
0542 }
0543 
0544 void RunController::slotExecute()
0545 {
0546     Q_D(RunController);
0547 
0548     if (d->launchConfigurations.isEmpty()) {
0549         showConfigurationDialog();
0550     }
0551 
0552     if (!d->launchConfigurations.isEmpty()) {
0553         executeDefaultLaunch( QStringLiteral("execute") );
0554     }
0555 }
0556 
0557 void KDevelop::RunController::showConfigurationDialog() const
0558 {
0559     LaunchConfigurationDialog dlg;
0560     dlg.exec();
0561 }
0562 
0563 LaunchConfiguration* KDevelop::RunController::defaultLaunch() const
0564 {
0565     Q_D(const RunController);
0566 
0567     QAction* projectAction = d->currentTargetAction->currentAction();
0568     if( projectAction )
0569         return static_cast<LaunchConfiguration*>(qvariant_cast<void*>(projectAction->data()));
0570     return nullptr;
0571 }
0572 
0573 void KDevelop::RunController::registerJob(KJob * job)
0574 {
0575     Q_D(RunController);
0576 
0577     if (!job)
0578         return;
0579 
0580     if (!(job->capabilities() & KJob::Killable)) {
0581         // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187
0582         qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown.";
0583     }
0584 
0585     if (!d->jobs.contains(job)) {
0586         QAction* stopJobAction = nullptr;
0587         if (Core::self()->setupFlags() != Core::NoUi) {
0588             stopJobAction = new QAction(job->objectName().isEmpty() ? i18nc("@item:inmenu", "<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this);
0589             stopJobAction->setData(QVariant::fromValue(static_cast<void*>(job)));
0590             d->stopJobsMenu->addAction(stopJobAction);
0591             connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob);
0592 
0593             job->setUiDelegate( new KDialogJobUiDelegate() );
0594         }
0595 
0596         d->jobs.insert(job, stopJobAction);
0597 
0598         connect( job, &KJob::finished, this, &RunController::finished );
0599         connect( job, &KJob::destroyed, this, &RunController::jobDestroyed );
0600         connect(job, &KJob::percentChanged, this, &RunController::jobPercentChanged);
0601 
0602         IRunController::registerJob(job);
0603 
0604         emit jobRegistered(job);
0605     }
0606 
0607     job->start();
0608 
0609     checkState();
0610 }
0611 
0612 void KDevelop::RunController::unregisterJob(KJob * job)
0613 {
0614     Q_D(RunController);
0615 
0616     IRunController::unregisterJob(job);
0617 
0618     Q_ASSERT(d->jobs.contains(job));
0619 
0620     // Delete the stop job action
0621     QAction *action = d->jobs.take(job);
0622     if (action)
0623         action->deleteLater();
0624 
0625     checkState();
0626 
0627     emit jobUnregistered(job);
0628 }
0629 
0630 void KDevelop::RunController::checkState()
0631 {
0632     Q_D(RunController);
0633 
0634     bool running = false;
0635 
0636     int jobCount = 0;
0637     int totalProgress = 0;
0638 
0639     for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) {
0640         KJob *job = it.key();
0641 
0642         if (!job->isSuspended()) {
0643             running = true;
0644 
0645             ++jobCount;
0646             totalProgress += job->percent();
0647         }
0648     }
0649 
0650     d->unityLauncher->setProgressVisible(running);
0651     if (jobCount > 0) {
0652         d->unityLauncher->setProgress((totalProgress + 1) / jobCount);
0653     } else {
0654         d->unityLauncher->setProgress(0);
0655     }
0656 
0657     if ( ( d->state != Running ? false : true ) == running ) {
0658         d->state = running ? Running : Idle;
0659         emit runStateChanged(d->state);
0660     }
0661 
0662     if (Core::self()->setupFlags() != Core::NoUi) {
0663         d->stopAction->setEnabled(running);
0664         d->stopJobsMenu->setEnabled(running);
0665     }
0666 }
0667 
0668 void KDevelop::RunController::stopAllProcesses()
0669 {
0670     Q_D(RunController);
0671 
0672     // composite jobs might remove child jobs, see also:
0673     // https://bugs.kde.org/show_bug.cgi?id=258904
0674     const auto jobs = d->jobs.keys();
0675     for (KJob* job : jobs) {
0676         // now we check the real list whether it was deleted
0677         if (!d->jobs.contains(job))
0678             continue;
0679         if (job->capabilities() & KJob::Killable) {
0680             job->kill(KJob::EmitResult);
0681         } else {
0682             qCWarning(SHELL) << "cannot stop non-killable job: " << job;
0683         }
0684     }
0685 }
0686 
0687 void KDevelop::RunController::slotKillJob()
0688 {
0689     auto* action = qobject_cast<QAction*>(sender());
0690     Q_ASSERT(action);
0691 
0692     KJob* job = static_cast<KJob*>(qvariant_cast<void*>(action->data()));
0693     if (job->capabilities() & KJob::Killable)
0694         job->kill();
0695 }
0696 
0697 void KDevelop::RunController::finished(KJob * job)
0698 {
0699     unregisterJob(job);
0700 
0701     switch (job->error()) {
0702         case KJob::NoError:
0703         case KJob::KilledJobError:
0704         case OutputJob::FailedShownError:
0705             break;
0706 
0707         default:
0708         {
0709             auto* message = new Sublime::Message(job->errorString(), Sublime::Message::Error);
0710             Core::self()->uiController()->postMessage(message);
0711         }
0712     }
0713 }
0714 
0715 void RunController::jobDestroyed(QObject* job)
0716 {
0717     Q_D(RunController);
0718 
0719     KJob* kjob = static_cast<KJob*>(job);
0720     if (d->jobs.contains(kjob)) {
0721         qCWarning(SHELL) << "job destroyed without emitting finished signal!";
0722         unregisterJob(kjob);
0723     }
0724 }
0725 
0726 void RunController::jobPercentChanged()
0727 {
0728     checkState();
0729 }
0730 
0731 void KDevelop::RunController::suspended(KJob * job)
0732 {
0733     Q_UNUSED(job);
0734 
0735     checkState();
0736 }
0737 
0738 void KDevelop::RunController::resumed(KJob * job)
0739 {
0740     Q_UNUSED(job);
0741 
0742     checkState();
0743 }
0744 
0745 QList< KJob * > KDevelop::RunController::currentJobs() const
0746 {
0747     Q_D(const RunController);
0748 
0749     return d->jobs.keys();
0750 }
0751 
0752 QList<ILaunchConfiguration*> RunController::launchConfigurations() const
0753 {
0754     QList<ILaunchConfiguration*> configs;
0755     const auto configsInternal = launchConfigurationsInternal();
0756     configs.reserve(configsInternal.size());
0757     for (LaunchConfiguration* config : configsInternal) {
0758         configs << config;
0759     }
0760     return configs;
0761 }
0762 
0763 QList<LaunchConfiguration*> RunController::launchConfigurationsInternal() const
0764 {
0765     Q_D(const RunController);
0766 
0767     return d->launchConfigurations;
0768 }
0769 
0770 QList<LaunchConfigurationType*> RunController::launchConfigurationTypes() const
0771 {
0772     Q_D(const RunController);
0773 
0774     return d->launchConfigurationTypes.values();
0775 }
0776 
0777 void RunController::addConfigurationType( LaunchConfigurationType* type )
0778 {
0779     Q_D(RunController);
0780 
0781     if( !d->launchConfigurationTypes.contains( type->id() ) )
0782     {
0783         d->launchConfigurationTypes.insert( type->id(), type );
0784     }
0785 }
0786 
0787 void RunController::removeConfigurationType( LaunchConfigurationType* type )
0788 {
0789     Q_D(RunController);
0790 
0791     const auto oldLaunchConfigurations = d->launchConfigurations;
0792     for (LaunchConfiguration* l : oldLaunchConfigurations) {
0793         if( l->type() == type )
0794         {
0795             removeLaunchConfigurationInternal( l );
0796         }
0797     }
0798     d->launchConfigurationTypes.remove( type->id() );
0799 }
0800 
0801 void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode)
0802 {
0803     Q_D(RunController);
0804 
0805     if( !d->launchModes.contains( mode->id() ) )
0806     {
0807         d->launchModes.insert( mode->id(), mode );
0808     }
0809 }
0810 
0811 QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const
0812 {
0813     Q_D(const RunController);
0814 
0815     return d->launchModes.values();
0816 }
0817 
0818 void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode)
0819 {
0820     Q_D(RunController);
0821 
0822     d->launchModes.remove( mode->id() );
0823 }
0824 
0825 KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const
0826 {
0827     Q_D(const RunController);
0828 
0829     auto it = d->launchModes.find( id );
0830     if( it != d->launchModes.end() )
0831     {
0832         return it.value();
0833     }
0834     return nullptr;
0835 }
0836 
0837 void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l)
0838 {
0839     Q_D(RunController);
0840 
0841     if( !d->launchConfigurations.contains( l ) )
0842     {
0843         d->addLaunchAction( l );
0844         d->launchConfigurations << l;
0845         if( !d->currentTargetAction->currentAction() )
0846         {
0847             if( !d->currentTargetAction->actions().isEmpty() )
0848             {
0849                 d->currentTargetAction->actions().at(0)->setChecked( true );
0850             }
0851         }
0852         connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged );
0853     }
0854 }
0855 
0856 void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l)
0857 {
0858     KConfigGroup launcherGroup;
0859     if( l->project() ) {
0860         launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() );
0861     } else {
0862         launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
0863     }
0864     QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
0865     configs.removeAll( l->configGroupName() );
0866     launcherGroup.deleteGroup( l->configGroupName() );
0867     launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs );
0868     launcherGroup.sync();
0869 
0870     removeLaunchConfigurationInternal( l );
0871 }
0872 
0873 void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l)
0874 {
0875     Q_D(RunController);
0876 
0877     const auto actions = d->currentTargetAction->actions();
0878     for (QAction* a : actions) {
0879         if( static_cast<LaunchConfiguration*>( a->data().value<void*>() ) == l ) {
0880             bool wasSelected = a->isChecked();
0881             d->currentTargetAction->removeAction( a );
0882             if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) {
0883                 d->currentTargetAction->actions().at(0)->setChecked( true );
0884             }
0885             break;
0886         }
0887     }
0888 
0889     d->launchConfigurations.removeAll( l );
0890 
0891     delete l;
0892 }
0893 
0894 void KDevelop::RunController::executeDefaultLaunch(const QString& runMode)
0895 {
0896     if (auto dl = defaultLaunch()) {
0897         execute(runMode, dl);
0898     } else {
0899         qCWarning(SHELL) << "no default launch!";
0900     }
0901 }
0902 
0903 void RunController::setDefaultLaunch(ILaunchConfiguration* l)
0904 {
0905     Q_D(RunController);
0906 
0907     const auto actions = d->currentTargetAction->actions();
0908     for (QAction* a : actions) {
0909         if( static_cast<ILaunchConfiguration*>( a->data().value<void*>() ) == l )
0910         {
0911             a->setChecked(true);
0912             break;
0913         }
0914     }
0915 }
0916 
0917 bool launcherNameExists(const QString& name)
0918 {
0919     const auto configs = Core::self()->runControllerInternal()->launchConfigurations();
0920 
0921     return std::any_of(configs.begin(), configs.end(), [&](ILaunchConfiguration* config) {
0922         return (config->name() == name);
0923     });
0924 }
0925 
0926 QString makeUnique(const QString& name)
0927 {
0928     if(launcherNameExists(name)) {
0929         for(int i=2; ; i++) {
0930             QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i);
0931             if(!launcherNameExists(proposed)) {
0932                 return proposed;
0933             }
0934         }
0935     }
0936     return name;
0937 }
0938 
0939 ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type,
0940                                                                  const QPair<QString,QString>& launcher,
0941                                                                  IProject* project, const QString& name )
0942 {
0943     KConfigGroup launchGroup;
0944     if( project )
0945     {
0946         launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() );
0947 
0948     } else
0949     {
0950         launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
0951 
0952     }
0953     QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
0954     uint num = 0;
0955     QString baseName = QStringLiteral("Launch Configuration");
0956     while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) )
0957     {
0958         num++;
0959     }
0960     QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num );
0961     KConfigGroup launchConfigGroup = launchGroup.group( groupName );
0962     QString cfgName = name;
0963     if( name.isEmpty() )
0964     {
0965         cfgName = i18n("New %1 Launcher", type->name() );
0966         cfgName = makeUnique(cfgName);
0967     }
0968 
0969     launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName );
0970     launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() );
0971     launchConfigGroup.sync();
0972     configs << groupName;
0973     launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs );
0974     launchGroup.sync();
0975     auto* l = new LaunchConfiguration( launchConfigGroup, project );
0976     l->setLauncherForMode( launcher.first, launcher.second );
0977     Core::self()->runControllerInternal()->addLaunchConfiguration( l );
0978     return l;
0979 }
0980 
0981 QItemDelegate * KDevelop::RunController::delegate() const
0982 {
0983     Q_D(const RunController);
0984 
0985     return d->delegate;
0986 }
0987 
0988 ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent)
0989 {
0990     Q_D(RunController);
0991 
0992     d->launchAsInfo.clear();
0993     d->contextItem = nullptr;
0994     ContextMenuExtension ext;
0995     if( ctx->type() == Context::ProjectItemContext ) {
0996         auto* prjctx = static_cast<KDevelop::ProjectItemContext*>(ctx);
0997         if( prjctx->items().count() == 1 )
0998         {
0999             ProjectBaseItem* itm = prjctx->items().at( 0 );
1000             int i = 0;
1001             for (ILaunchMode* mode : qAsConst(d->launchModes)) {
1002                 auto* menu = new KActionMenu(i18nc("@title:menu", "%1 As...", mode->name() ), parent);
1003                 const auto types = launchConfigurationTypes();
1004                 for (LaunchConfigurationType* type : types) {
1005                     bool hasLauncher = false;
1006                     const auto launchers = type->launchers();
1007                     for (ILauncher* launcher : launchers) {
1008                         if( launcher->supportedModes().contains( mode->id() ) )
1009                         {
1010                             hasLauncher = true;
1011                         }
1012                     }
1013                     if( hasLauncher && type->canLaunch(itm) )
1014                     {
1015                         d->launchAsInfo[i] = qMakePair( type->id(), mode->id() );
1016                         auto* act = new QAction(menu);
1017                         act->setText( type->name() );
1018                         qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name();
1019                         connect(act, &QAction::triggered,
1020                                 this, [this, i] () { Q_D(RunController); d->launchAs(i); } );
1021                         menu->addAction(act);
1022                         i++;
1023                     }
1024                 }
1025                 if( menu->menu()->actions().count() > 0 )
1026                 {
1027                     ext.addAction( ContextMenuExtension::RunGroup, menu);
1028                 } else {
1029                     delete menu;
1030                 }
1031             }
1032             if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 )
1033             {
1034                 d->contextItem = itm;
1035             }
1036         }
1037     }
1038     return ext;
1039 }
1040 
1041 
1042 
1043 RunDelegate::RunDelegate( QObject* parent )
1044 : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ),
1045 errorBrush( KColorScheme::View, KColorScheme::NegativeText )
1046 {
1047 }
1048 
1049 void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
1050 {
1051     QStyleOptionViewItem opt = option;
1052 //     if( status.isValid() && status.canConvert<KDevelop::IRunProvider::OutputTypes>() )
1053 //     {
1054 //         IRunProvider::OutputTypes type = status.value<KDevelop::IRunProvider::OutputTypes>();
1055 //         if( type == IRunProvider::RunProvider )
1056 //         {
1057 //             opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) );
1058 //         } else if( type == IRunProvider::StandardError )
1059 //         {
1060 //             opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) );
1061 //         }
1062 //     }
1063     QItemDelegate::paint(painter, opt, index);
1064 }
1065 
1066 
1067 #include "moc_runcontroller.cpp"