1 /*
2     SPDX-FileCopyrightText: 2007-2008 Hamish Rodda <rodda@kde.org>
3     SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "runcontroller.h"
9 
10 #include <QDBusConnection>
11 #include <QPalette>
12 
13 #include <KAboutData>
14 #include <KActionCollection>
15 #include <KActionMenu>
16 #include <kcoreaddons_version.h>
17 #include <KDialogJobUiDelegate>
18 #include <KLocalizedString>
19 #include <KSelectAction>
20 #include <kwidgetsaddons_version.h>
21 
22 #include <interfaces/iproject.h>
23 #include <interfaces/idocumentcontroller.h>
24 #include <interfaces/ilauncher.h>
25 #include <interfaces/ilaunchmode.h>
26 #include <interfaces/launchconfigurationtype.h>
27 #include <outputview/outputjob.h>
28 #include <project/projectmodel.h>
29 #include <sublime/message.h>
30 
31 #include "core.h"
32 #include "uicontroller.h"
33 #include "projectcontroller.h"
34 #include "mainwindow.h"
35 #include "launchconfiguration.h"
36 #include "launchconfigurationdialog.h"
37 #include "unitylauncher.h"
38 #include "debug.h"
39 #include <interfaces/isession.h>
40 
41 #include <interfaces/contextmenuextension.h>
42 #include <interfaces/context.h>
43 #include <sublime/area.h>
44 
45 using namespace KDevelop;
46 
47 namespace {
48 namespace Strings {
LaunchConfigurationsGroup()49 QString LaunchConfigurationsGroup()
50 {
51     return QStringLiteral("Launch");
52 }
53 
LaunchConfigurationsListEntry()54 QString LaunchConfigurationsListEntry()
55 {
56     return QStringLiteral("Launch Configurations");
57 }
58 
CurrentLaunchConfigProjectEntry()59 QString CurrentLaunchConfigProjectEntry()
60 {
61     return QStringLiteral("Current Launch Config Project");
62 }
63 
CurrentLaunchConfigNameEntry()64 QString CurrentLaunchConfigNameEntry()
65 {
66     return QStringLiteral("Current Launch Config GroupName");
67 }
68 
ConfiguredFromProjectItemEntry()69 QString ConfiguredFromProjectItemEntry()
70 {
71     return QStringLiteral("Configured from ProjectItem");
72 }
73 }
74 }
75 
76 using Target = QPair<QString, IProject*>;
77 Q_DECLARE_METATYPE(Target)
78 
79 
80 //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs
81 //TODO: Doesn't auto-select launch configs opened from projects
82 
83 class DebugMode : public ILaunchMode
84 {
85 public:
DebugMode()86     DebugMode() {}
icon() const87     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("debug-run")); }
id() const88     QString id() const override { return QStringLiteral("debug"); }
name() const89     QString name() const override { return i18nc("launch mode", "Debug"); }
90 };
91 
92 class ProfileMode : public ILaunchMode
93 {
94 public:
ProfileMode()95     ProfileMode() {}
icon() const96     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("office-chart-area")); }
id() const97     QString id() const override { return QStringLiteral("profile"); }
name() const98     QString name() const override { return i18nc("launch mode", "Profile"); }
99 };
100 
101 class ExecuteMode : public ILaunchMode
102 {
103 public:
ExecuteMode()104     ExecuteMode() {}
icon() const105     QIcon icon() const override { return QIcon::fromTheme(QStringLiteral("system-run")); }
id() const106     QString id() const override { return QStringLiteral("execute"); }
name() const107     QString name() const override { return i18nc("launch mode", "Execute"); }
108 };
109 
110 class KDevelop::RunControllerPrivate
111 {
112 public:
113     QItemDelegate* delegate;
114 
115     IRunController::State state;
116 
117     RunController* q;
118 
119     QHash<KJob*, QAction*> jobs;
120     QAction* stopAction;
121     KActionMenu* stopJobsMenu;
122     QAction* runAction;
123     QAction* dbgAction;
124     KSelectAction* currentTargetAction;
125     QMap<QString,LaunchConfigurationType*> launchConfigurationTypes;
126     QList<LaunchConfiguration*> launchConfigurations;
127     QMap<QString,ILaunchMode*> launchModes;
128     QMap<int,QPair<QString,QString> > launchAsInfo;
129     KDevelop::ProjectBaseItem* contextItem;
130     DebugMode* debugMode;
131     ExecuteMode* executeMode;
132     ProfileMode* profileMode;
133     UnityLauncher* unityLauncher;
134 
hasLaunchConfigType(const QString & typeId)135     bool hasLaunchConfigType( const QString& typeId )
136     {
137         return launchConfigurationTypes.contains( typeId );
138     }
saveCurrentLaunchAction()139     void saveCurrentLaunchAction()
140     {
141         if (!currentTargetAction) return;
142 
143         if( currentTargetAction->currentAction() )
144         {
145             KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
146             LaunchConfiguration* l = static_cast<LaunchConfiguration*>( currentTargetAction->currentAction()->data().value<void*>() );
147             grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : QString() );
148             grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() );
149             grp.sync();
150         }
151     }
152 
launchActionText(LaunchConfiguration * l)153     QString launchActionText( LaunchConfiguration* l )
154     {
155         QString label;
156         if( l->project() )
157         {
158             label = QStringLiteral("%1 : %2").arg( l->project()->name(), l->name());
159         } else
160         {
161             label = l->name();
162         }
163         return label;
164     }
165 
launchAs(int id)166     void launchAs( int id )
167     {
168         //qCDebug(SHELL) << "Launching id:" << id;
169         QPair<QString,QString> info = launchAsInfo[id];
170         //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second;
171         LaunchConfigurationType* type = launchConfigurationTypeForId( info.first );
172         ILaunchMode* mode = q->launchModeForId( info.second );
173 
174         //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id();
175         if( type && mode )
176         {
177             const auto launchers = type->launchers();
178             auto it = std::find_if(launchers.begin(), launchers.end(), [&](ILauncher* l) {
179                 //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes();
180                 return (l->supportedModes().contains(mode->id()));
181             });
182             if (it != launchers.end()) {
183                 ILauncher* launcher = *it;
184 
185                 QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index());
186                 auto it = std::find_if(launchConfigurations.constBegin(), launchConfigurations.constEnd(),
187                                        [&] (LaunchConfiguration* l) {
188                     QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList());
189                     if (l->type() == type && path == itemPath) {
190                         qCDebug(SHELL) << "already generated ilaunch" << path;
191                         return true;
192                     }
193                     return false;
194                 });
195                 ILaunchConfiguration* ilaunch = (it != launchConfigurations.constEnd()) ? *it : nullptr;
196 
197                 if (!ilaunch) {
198                     ilaunch = q->createLaunchConfiguration( type,
199                                                             qMakePair( mode->id(), launcher->id() ),
200                                                             contextItem->project(),
201                                                             contextItem->text() );
202                     auto* launch = static_cast<LaunchConfiguration*>(ilaunch);
203                     type->configureLaunchFromItem( launch->config(), contextItem );
204                     launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath);
205                     //qCDebug(SHELL) << "created config, launching";
206                 } else {
207                     //qCDebug(SHELL) << "reusing generated config, launching";
208                 }
209                 q->setDefaultLaunch(ilaunch);
210                 q->execute( mode->id(), ilaunch );
211             }
212         }
213     }
214 
updateCurrentLaunchAction()215     void updateCurrentLaunchAction()
216     {
217         if (!currentTargetAction) return;
218 
219         KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
220         QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" );
221         QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" );
222 
223         LaunchConfiguration* l = nullptr;
224         if( currentTargetAction->currentAction() )
225         {
226             l = static_cast<LaunchConfiguration*>( currentTargetAction->currentAction()->data().value<void*>() );
227         } else if( !launchConfigurations.isEmpty() )
228         {
229             l = launchConfigurations.at( 0 );
230         }
231 
232         if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) )
233         {
234             const auto actions = currentTargetAction->actions();
235             for (QAction* a : actions) {
236                 LaunchConfiguration* l = static_cast<LaunchConfiguration*>( qvariant_cast<void*>( a->data() ) );
237                 if( currentLaunchName == l->configGroupName()
238                     && ( ( currentLaunchProject.isEmpty() && !l->project() )
239                          || ( l->project() && l->project()->name() == currentLaunchProject ) ) )
240                 {
241                     a->setChecked( true );
242                     break;
243                 }
244             }
245         }
246         if( !currentTargetAction->currentAction() )
247         {
248             qCDebug(SHELL) << "oops no current action, using first if list is non-empty";
249             if( !currentTargetAction->actions().isEmpty() )
250             {
251                 currentTargetAction->actions().at(0)->setChecked( true );
252             }
253         }
254     }
255 
addLaunchAction(LaunchConfiguration * l)256     void addLaunchAction( LaunchConfiguration* l )
257     {
258         if (!currentTargetAction) return;
259 
260         QAction* action = currentTargetAction->addAction(launchActionText( l ));
261         action->setData(QVariant::fromValue<void*>(l));
262     }
readLaunchConfigs(const KSharedConfigPtr & cfg,IProject * prj)263     void readLaunchConfigs( const KSharedConfigPtr& cfg, IProject* prj )
264     {
265         KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup());
266         const QStringList configs = group.readEntry(Strings::LaunchConfigurationsListEntry(), QStringList());
267 
268         for (const QString& cfg : configs) {
269             KConfigGroup grp = group.group( cfg );
270             if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) )
271             {
272                 q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) );
273             }
274         }
275     }
launchConfigurationTypeForId(const QString & id)276     LaunchConfigurationType* launchConfigurationTypeForId( const QString& id )
277     {
278         QMap<QString, LaunchConfigurationType*>::iterator it = launchConfigurationTypes.find( id );
279         if( it != launchConfigurationTypes.end() )
280         {
281             return it.value();
282         } else
283         {
284             qCWarning(SHELL) << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys();
285         }
286         return nullptr;
287 
288     }
289 
290 };
291 
RunController(QObject * parent)292 RunController::RunController(QObject *parent)
293     : IRunController(parent)
294     , d_ptr(new RunControllerPrivate)
295 {
296     Q_D(RunController);
297 
298     setObjectName(QStringLiteral("RunController"));
299 
300     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kdevelop/RunController"),
301         this, QDBusConnection::ExportScriptableSlots);
302 
303     // TODO: need to implement compile only if needed before execute
304     // TODO: need to implement abort all running programs when project closed
305 
306     d->currentTargetAction = nullptr;
307     d->state = Idle;
308     d->q = this;
309     d->delegate = new RunDelegate(this);
310     d->contextItem = nullptr;
311     d->executeMode = nullptr;
312     d->debugMode = nullptr;
313     d->profileMode = nullptr;
314 
315     d->unityLauncher = new UnityLauncher(this);
316     d->unityLauncher->setLauncherId(KAboutData::applicationData().desktopFileName());
317 
318     if(!(Core::self()->setupFlags() & Core::NoUi)) {
319         // Note that things like registerJob() do not work without the actions, it'll simply crash.
320         setupActions();
321     }
322 }
323 
324 RunController::~RunController() = default;
325 
launchChanged(LaunchConfiguration * l)326 void KDevelop::RunController::launchChanged( LaunchConfiguration* l )
327 {
328     Q_D(RunController);
329 
330     const auto actions = d->currentTargetAction->actions();
331     for (QAction* a : actions) {
332         if( static_cast<LaunchConfiguration*>( a->data().value<void*>() ) == l )
333         {
334             a->setText( d->launchActionText( l ) );
335             break;
336         }
337     }
338 }
339 
cleanup()340 void RunController::cleanup()
341 {
342     Q_D(RunController);
343 
344     delete d->executeMode;
345     d->executeMode = nullptr;
346     delete d->profileMode;
347     d->profileMode = nullptr;
348     delete d->debugMode;
349     d->debugMode = nullptr;
350 
351     stopAllProcesses();
352     d->saveCurrentLaunchAction();
353 }
354 
initialize()355 void RunController::initialize()
356 {
357     Q_D(RunController);
358 
359     d->executeMode = new ExecuteMode();
360     addLaunchMode( d->executeMode );
361     d->profileMode = new ProfileMode();
362     addLaunchMode( d->profileMode );
363     d->debugMode = new DebugMode;
364     addLaunchMode( d->debugMode );
365     d->readLaunchConfigs( Core::self()->activeSession()->config(), nullptr );
366 
367     const auto projects = Core::self()->projectController()->projects();
368     for (IProject* project : projects) {
369         slotProjectOpened(project);
370     }
371     connect(Core::self()->projectController(), &IProjectController::projectOpened,
372             this, &RunController::slotProjectOpened);
373     connect(Core::self()->projectController(), &IProjectController::projectClosing,
374             this, &RunController::slotProjectClosing);
375     connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged,
376              this, &RunController::slotRefreshProject);
377 
378     if( (Core::self()->setupFlags() & Core::NoUi) == 0 )
379     {
380         // Only do this in GUI mode
381         d->updateCurrentLaunchAction();
382     }
383 }
384 
execute(const QString & runMode,ILaunchConfiguration * launch)385 KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch)
386 {
387     if( !launch )
388     {
389         qCDebug(SHELL) << "execute called without launch config!";
390         return nullptr;
391     }
392     auto* run = static_cast<LaunchConfiguration*>(launch);
393     //TODO: Port to launch framework, probably needs to be part of the launcher
394     //if(!run.dependencies().isEmpty())
395     //    ICore::self()->documentController()->saveAllDocuments(IDocument::Silent);
396 
397     //foreach(KJob* job, run.dependencies())
398     //{
399     //    jobs.append(job);
400     //}
401 
402     qCDebug(SHELL) << "mode:" << runMode;
403     QString launcherId = run->launcherForMode( runMode );
404     qCDebug(SHELL) << "launcher id:" << launcherId;
405 
406     ILauncher* launcher = run->type()->launcherForId( launcherId );
407 
408     if( !launcher ) {
409         const QString messageText = i18n("The current launch configuration does not support the '%1' mode.", runMode);
410         auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
411         ICore::self()->uiController()->postMessage(message);
412         return nullptr;
413     }
414 
415     KJob* launchJob = launcher->start(runMode, run);
416     registerJob(launchJob);
417     return launchJob;
418 }
419 
setupActions()420 void RunController::setupActions()
421 {
422     Q_D(RunController);
423 
424     QAction* action;
425 
426     // TODO not multi-window friendly, FIXME
427     KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection();
428 
429     action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action", "Configure Launches..."), this);
430     ac->addAction(QStringLiteral("configure_launches"), action);
431     action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item
432     action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog"));
433     action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones."));
434     connect(action, &QAction::triggered, this, &RunController::showConfigurationDialog);
435 
436     d->runAction = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18nc("@action", "Execute Launch"), this);
437     d->runAction->setIconText( i18nc("@action Short text for 'Execute Launch' used in the toolbar", "Execute") );
438     ac->setDefaultShortcut(d->runAction, Qt::SHIFT | Qt::Key_F9);
439     d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch"));
440     d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration."));
441     ac->addAction(QStringLiteral("run_execute"), d->runAction);
442     connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute);
443 
444     d->dbgAction = new QAction( QIcon::fromTheme(QStringLiteral("debug-run")), i18nc("@action", "Debug Launch"), this);
445     ac->setDefaultShortcut(d->dbgAction, Qt::ALT | Qt::Key_F9);
446     d->dbgAction->setIconText( i18nc("@action Short text for 'Debug Launch' used in the toolbar", "Debug") );
447     d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch"));
448     d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger."));
449     ac->addAction(QStringLiteral("run_debug"), d->dbgAction);
450     connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug);
451     Core::self()->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(d->dbgAction);
452 
453 //     TODO: at least get a profile target, it's sad to have the menu entry without a profiler
454 //     QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this);
455 //     profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch"));
456 //     profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler."));
457 //     ac->addAction("run_profile", profileAction);
458 //     connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile()));
459 
460     action = d->stopAction = new QAction( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop All Jobs"), this);
461     action->setIconText(i18nc("@action Short text for 'Stop All Jobs' used in the toolbar", "Stop All"));
462     // Ctrl+Escape would be nicer, but that is taken by the ksysguard desktop shortcut
463     ac->setDefaultShortcut( action, QKeySequence(QStringLiteral("Ctrl+Shift+Escape")));
464     action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs"));
465     action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped."));
466     action->setEnabled(false);
467     ac->addAction(QStringLiteral("run_stop_all"), action);
468     connect(action, &QAction::triggered, this, &RunController::stopAllProcesses);
469     Core::self()->uiControllerInternal()->area(0, QStringLiteral("debug"))->addAction(action);
470 
471     action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme(QStringLiteral("process-stop")), i18nc("@action", "Stop"), this);
472 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 77, 0)
473     d->stopJobsMenu->setPopupMode(QToolButton::InstantPopup);
474 #else
475     d->stopJobsMenu->setDelayed(false);
476 #endif
477     action->setIconText(i18nc("@action Short text for 'Stop' used in the toolbar", "Stop"));
478     action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs"));
479     action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually."));
480     action->setEnabled(false);
481     ac->addAction(QStringLiteral("run_stop_menu"), action);
482 
483     d->currentTargetAction = new KSelectAction( i18nc("@title:menu", "Current Launch Configuration"), this);
484     d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration"));
485     d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked."));
486     ac->addAction(QStringLiteral("run_default_target"), d->currentTargetAction);
487 }
488 
launchConfigurationTypeForId(const QString & id)489 LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id )
490 {
491     Q_D(RunController);
492 
493     return d->launchConfigurationTypeForId( id );
494 }
495 
slotProjectOpened(KDevelop::IProject * project)496 void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project)
497 {
498     Q_D(RunController);
499 
500     d->readLaunchConfigs( project->projectConfiguration(), project );
501     d->updateCurrentLaunchAction();
502 }
503 
slotProjectClosing(KDevelop::IProject * project)504 void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project)
505 {
506     Q_D(RunController);
507 
508     if (!d->currentTargetAction) return;
509 
510     const auto actions = d->currentTargetAction->actions();
511     for (QAction* action : actions) {
512         LaunchConfiguration* l = static_cast<LaunchConfiguration*>(qvariant_cast<void*>(action->data()));
513         if ( project == l->project() ) {
514             l->save();
515             d->launchConfigurations.removeAll(l);
516             delete l;
517             bool wasSelected = action->isChecked();
518             delete action;
519             if (wasSelected && !d->currentTargetAction->actions().isEmpty())
520                 d->currentTargetAction->actions().at(0)->setChecked(true);
521         }
522     }
523 }
524 
slotRefreshProject(KDevelop::IProject * project)525 void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project)
526 {
527     slotProjectClosing(project);
528     slotProjectOpened(project);
529 }
530 
slotDebug()531 void RunController::slotDebug()
532 {
533     Q_D(RunController);
534 
535     if (d->launchConfigurations.isEmpty()) {
536         showConfigurationDialog();
537     }
538 
539     if (!d->launchConfigurations.isEmpty()) {
540         executeDefaultLaunch( QStringLiteral("debug") );
541     }
542 }
543 
slotProfile()544 void RunController::slotProfile()
545 {
546     Q_D(RunController);
547 
548     if (d->launchConfigurations.isEmpty()) {
549         showConfigurationDialog();
550     }
551 
552     if (!d->launchConfigurations.isEmpty()) {
553         executeDefaultLaunch( QStringLiteral("profile") );
554     }
555 }
556 
slotExecute()557 void RunController::slotExecute()
558 {
559     Q_D(RunController);
560 
561     if (d->launchConfigurations.isEmpty()) {
562         showConfigurationDialog();
563     }
564 
565     if (!d->launchConfigurations.isEmpty()) {
566         executeDefaultLaunch( QStringLiteral("execute") );
567     }
568 }
569 
showConfigurationDialog() const570 void KDevelop::RunController::showConfigurationDialog() const
571 {
572     LaunchConfigurationDialog dlg;
573     dlg.exec();
574 }
575 
defaultLaunch() const576 LaunchConfiguration* KDevelop::RunController::defaultLaunch() const
577 {
578     Q_D(const RunController);
579 
580     QAction* projectAction = d->currentTargetAction->currentAction();
581     if( projectAction )
582         return static_cast<LaunchConfiguration*>(qvariant_cast<void*>(projectAction->data()));
583     return nullptr;
584 }
585 
registerJob(KJob * job)586 void KDevelop::RunController::registerJob(KJob * job)
587 {
588     Q_D(RunController);
589 
590     if (!job)
591         return;
592 
593     if (!(job->capabilities() & KJob::Killable)) {
594         // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187
595         qCWarning(SHELL) << "non-killable job" << job << "registered - this might lead to crashes on shutdown.";
596     }
597 
598     if (!d->jobs.contains(job)) {
599         QAction* stopJobAction = nullptr;
600         if (Core::self()->setupFlags() != Core::NoUi) {
601             stopJobAction = new QAction(job->objectName().isEmpty() ? i18nc("@item:inmenu", "<%1> Unnamed job", QString::fromUtf8(job->staticMetaObject.className())) : job->objectName(), this);
602             stopJobAction->setData(QVariant::fromValue(static_cast<void*>(job)));
603             d->stopJobsMenu->addAction(stopJobAction);
604             connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob);
605 
606             job->setUiDelegate( new KDialogJobUiDelegate() );
607         }
608 
609         d->jobs.insert(job, stopJobAction);
610 
611         connect( job, &KJob::finished, this, &RunController::finished );
612         connect( job, &KJob::destroyed, this, &RunController::jobDestroyed );
613 #if KCOREADDONS_VERSION < QT_VERSION_CHECK(5, 80, 0)
614         connect(job, QOverload<KJob*, unsigned long>::of(&KJob::percent), this, &RunController::jobPercentChanged);
615 #else
616         connect(job, &KJob::percentChanged, this, &RunController::jobPercentChanged);
617 #endif
618 
619         IRunController::registerJob(job);
620 
621         emit jobRegistered(job);
622     }
623 
624     job->start();
625 
626     checkState();
627 }
628 
unregisterJob(KJob * job)629 void KDevelop::RunController::unregisterJob(KJob * job)
630 {
631     Q_D(RunController);
632 
633     IRunController::unregisterJob(job);
634 
635     Q_ASSERT(d->jobs.contains(job));
636 
637     // Delete the stop job action
638     QAction *action = d->jobs.take(job);
639     if (action)
640         action->deleteLater();
641 
642     checkState();
643 
644     emit jobUnregistered(job);
645 }
646 
checkState()647 void KDevelop::RunController::checkState()
648 {
649     Q_D(RunController);
650 
651     bool running = false;
652 
653     int jobCount = 0;
654     int totalProgress = 0;
655 
656     for (auto it = d->jobs.constBegin(), end = d->jobs.constEnd(); it != end; ++it) {
657         KJob *job = it.key();
658 
659         if (!job->isSuspended()) {
660             running = true;
661 
662             ++jobCount;
663             totalProgress += job->percent();
664         }
665     }
666 
667     d->unityLauncher->setProgressVisible(running);
668     if (jobCount > 0) {
669         d->unityLauncher->setProgress((totalProgress + 1) / jobCount);
670     } else {
671         d->unityLauncher->setProgress(0);
672     }
673 
674     if ( ( d->state != Running ? false : true ) == running ) {
675         d->state = running ? Running : Idle;
676         emit runStateChanged(d->state);
677     }
678 
679     if (Core::self()->setupFlags() != Core::NoUi) {
680         d->stopAction->setEnabled(running);
681         d->stopJobsMenu->setEnabled(running);
682     }
683 }
684 
stopAllProcesses()685 void KDevelop::RunController::stopAllProcesses()
686 {
687     Q_D(RunController);
688 
689     // composite jobs might remove child jobs, see also:
690     // https://bugs.kde.org/show_bug.cgi?id=258904
691     const auto jobs = d->jobs.keys();
692     for (KJob* job : jobs) {
693         // now we check the real list whether it was deleted
694         if (!d->jobs.contains(job))
695             continue;
696         if (job->capabilities() & KJob::Killable) {
697             job->kill(KJob::EmitResult);
698         } else {
699             qCWarning(SHELL) << "cannot stop non-killable job: " << job;
700         }
701     }
702 }
703 
slotKillJob()704 void KDevelop::RunController::slotKillJob()
705 {
706     auto* action = qobject_cast<QAction*>(sender());
707     Q_ASSERT(action);
708 
709     KJob* job = static_cast<KJob*>(qvariant_cast<void*>(action->data()));
710     if (job->capabilities() & KJob::Killable)
711         job->kill();
712 }
713 
finished(KJob * job)714 void KDevelop::RunController::finished(KJob * job)
715 {
716     unregisterJob(job);
717 
718     switch (job->error()) {
719         case KJob::NoError:
720         case KJob::KilledJobError:
721         case OutputJob::FailedShownError:
722             break;
723 
724         default:
725         {
726             auto* message = new Sublime::Message(job->errorString(), Sublime::Message::Error);
727             Core::self()->uiController()->postMessage(message);
728         }
729     }
730 }
731 
jobDestroyed(QObject * job)732 void RunController::jobDestroyed(QObject* job)
733 {
734     Q_D(RunController);
735 
736     KJob* kjob = static_cast<KJob*>(job);
737     if (d->jobs.contains(kjob)) {
738         qCWarning(SHELL) << "job destroyed without emitting finished signal!";
739         unregisterJob(kjob);
740     }
741 }
742 
jobPercentChanged()743 void RunController::jobPercentChanged()
744 {
745     checkState();
746 }
747 
suspended(KJob * job)748 void KDevelop::RunController::suspended(KJob * job)
749 {
750     Q_UNUSED(job);
751 
752     checkState();
753 }
754 
resumed(KJob * job)755 void KDevelop::RunController::resumed(KJob * job)
756 {
757     Q_UNUSED(job);
758 
759     checkState();
760 }
761 
currentJobs() const762 QList< KJob * > KDevelop::RunController::currentJobs() const
763 {
764     Q_D(const RunController);
765 
766     return d->jobs.keys();
767 }
768 
launchConfigurations() const769 QList<ILaunchConfiguration*> RunController::launchConfigurations() const
770 {
771     QList<ILaunchConfiguration*> configs;
772     const auto configsInternal = launchConfigurationsInternal();
773     configs.reserve(configsInternal.size());
774     for (LaunchConfiguration* config : configsInternal) {
775         configs << config;
776     }
777     return configs;
778 }
779 
launchConfigurationsInternal() const780 QList<LaunchConfiguration*> RunController::launchConfigurationsInternal() const
781 {
782     Q_D(const RunController);
783 
784     return d->launchConfigurations;
785 }
786 
launchConfigurationTypes() const787 QList<LaunchConfigurationType*> RunController::launchConfigurationTypes() const
788 {
789     Q_D(const RunController);
790 
791     return d->launchConfigurationTypes.values();
792 }
793 
addConfigurationType(LaunchConfigurationType * type)794 void RunController::addConfigurationType( LaunchConfigurationType* type )
795 {
796     Q_D(RunController);
797 
798     if( !d->launchConfigurationTypes.contains( type->id() ) )
799     {
800         d->launchConfigurationTypes.insert( type->id(), type );
801     }
802 }
803 
removeConfigurationType(LaunchConfigurationType * type)804 void RunController::removeConfigurationType( LaunchConfigurationType* type )
805 {
806     Q_D(RunController);
807 
808     const auto oldLaunchConfigurations = d->launchConfigurations;
809     for (LaunchConfiguration* l : oldLaunchConfigurations) {
810         if( l->type() == type )
811         {
812             removeLaunchConfigurationInternal( l );
813         }
814     }
815     d->launchConfigurationTypes.remove( type->id() );
816 }
817 
addLaunchMode(KDevelop::ILaunchMode * mode)818 void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode)
819 {
820     Q_D(RunController);
821 
822     if( !d->launchModes.contains( mode->id() ) )
823     {
824         d->launchModes.insert( mode->id(), mode );
825     }
826 }
827 
launchModes() const828 QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const
829 {
830     Q_D(const RunController);
831 
832     return d->launchModes.values();
833 }
834 
removeLaunchMode(KDevelop::ILaunchMode * mode)835 void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode)
836 {
837     Q_D(RunController);
838 
839     d->launchModes.remove( mode->id() );
840 }
841 
launchModeForId(const QString & id) const842 KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const
843 {
844     Q_D(const RunController);
845 
846     auto it = d->launchModes.find( id );
847     if( it != d->launchModes.end() )
848     {
849         return it.value();
850     }
851     return nullptr;
852 }
853 
addLaunchConfiguration(KDevelop::LaunchConfiguration * l)854 void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l)
855 {
856     Q_D(RunController);
857 
858     if( !d->launchConfigurations.contains( l ) )
859     {
860         d->addLaunchAction( l );
861         d->launchConfigurations << l;
862         if( !d->currentTargetAction->currentAction() )
863         {
864             if( !d->currentTargetAction->actions().isEmpty() )
865             {
866                 d->currentTargetAction->actions().at(0)->setChecked( true );
867             }
868         }
869         connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged );
870     }
871 }
872 
removeLaunchConfiguration(KDevelop::LaunchConfiguration * l)873 void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l)
874 {
875     KConfigGroup launcherGroup;
876     if( l->project() ) {
877         launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() );
878     } else {
879         launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
880     }
881     QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
882     configs.removeAll( l->configGroupName() );
883     launcherGroup.deleteGroup( l->configGroupName() );
884     launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs );
885     launcherGroup.sync();
886 
887     removeLaunchConfigurationInternal( l );
888 }
889 
removeLaunchConfigurationInternal(LaunchConfiguration * l)890 void RunController::removeLaunchConfigurationInternal(LaunchConfiguration *l)
891 {
892     Q_D(RunController);
893 
894     const auto actions = d->currentTargetAction->actions();
895     for (QAction* a : actions) {
896         if( static_cast<LaunchConfiguration*>( a->data().value<void*>() ) == l ) {
897             bool wasSelected = a->isChecked();
898             d->currentTargetAction->removeAction( a );
899             if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) {
900                 d->currentTargetAction->actions().at(0)->setChecked( true );
901             }
902             break;
903         }
904     }
905 
906     d->launchConfigurations.removeAll( l );
907 
908     delete l;
909 }
910 
executeDefaultLaunch(const QString & runMode)911 void KDevelop::RunController::executeDefaultLaunch(const QString& runMode)
912 {
913     if (auto dl = defaultLaunch()) {
914         execute(runMode, dl);
915     } else {
916         qCWarning(SHELL) << "no default launch!";
917     }
918 }
919 
setDefaultLaunch(ILaunchConfiguration * l)920 void RunController::setDefaultLaunch(ILaunchConfiguration* l)
921 {
922     Q_D(RunController);
923 
924     const auto actions = d->currentTargetAction->actions();
925     for (QAction* a : actions) {
926         if( static_cast<ILaunchConfiguration*>( a->data().value<void*>() ) == l )
927         {
928             a->setChecked(true);
929             break;
930         }
931     }
932 }
933 
launcherNameExists(const QString & name)934 bool launcherNameExists(const QString& name)
935 {
936     const auto configs = Core::self()->runControllerInternal()->launchConfigurations();
937 
938     return std::any_of(configs.begin(), configs.end(), [&](ILaunchConfiguration* config) {
939         return (config->name() == name);
940     });
941 }
942 
makeUnique(const QString & name)943 QString makeUnique(const QString& name)
944 {
945     if(launcherNameExists(name)) {
946         for(int i=2; ; i++) {
947             QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i);
948             if(!launcherNameExists(proposed)) {
949                 return proposed;
950             }
951         }
952     }
953     return name;
954 }
955 
createLaunchConfiguration(LaunchConfigurationType * type,const QPair<QString,QString> & launcher,IProject * project,const QString & name)956 ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type,
957                                                                  const QPair<QString,QString>& launcher,
958                                                                  IProject* project, const QString& name )
959 {
960     KConfigGroup launchGroup;
961     if( project )
962     {
963         launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() );
964 
965     } else
966     {
967         launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() );
968 
969     }
970     QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() );
971     uint num = 0;
972     QString baseName = QStringLiteral("Launch Configuration");
973     while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) )
974     {
975         num++;
976     }
977     QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num );
978     KConfigGroup launchConfigGroup = launchGroup.group( groupName );
979     QString cfgName = name;
980     if( name.isEmpty() )
981     {
982         cfgName = i18n("New %1 Launcher", type->name() );
983         cfgName = makeUnique(cfgName);
984     }
985 
986     launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName );
987     launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() );
988     launchConfigGroup.sync();
989     configs << groupName;
990     launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs );
991     launchGroup.sync();
992     auto* l = new LaunchConfiguration( launchConfigGroup, project );
993     l->setLauncherForMode( launcher.first, launcher.second );
994     Core::self()->runControllerInternal()->addLaunchConfiguration( l );
995     return l;
996 }
997 
delegate() const998 QItemDelegate * KDevelop::RunController::delegate() const
999 {
1000     Q_D(const RunController);
1001 
1002     return d->delegate;
1003 }
1004 
contextMenuExtension(Context * ctx,QWidget * parent)1005 ContextMenuExtension RunController::contextMenuExtension(Context* ctx, QWidget* parent)
1006 {
1007     Q_D(RunController);
1008 
1009     d->launchAsInfo.clear();
1010     d->contextItem = nullptr;
1011     ContextMenuExtension ext;
1012     if( ctx->type() == Context::ProjectItemContext ) {
1013         auto* prjctx = static_cast<KDevelop::ProjectItemContext*>(ctx);
1014         if( prjctx->items().count() == 1 )
1015         {
1016             ProjectBaseItem* itm = prjctx->items().at( 0 );
1017             int i = 0;
1018             for (ILaunchMode* mode : qAsConst(d->launchModes)) {
1019                 auto* menu = new KActionMenu(i18nc("@title:menu", "%1 As...", mode->name() ), parent);
1020                 const auto types = launchConfigurationTypes();
1021                 for (LaunchConfigurationType* type : types) {
1022                     bool hasLauncher = false;
1023                     const auto launchers = type->launchers();
1024                     for (ILauncher* launcher : launchers) {
1025                         if( launcher->supportedModes().contains( mode->id() ) )
1026                         {
1027                             hasLauncher = true;
1028                         }
1029                     }
1030                     if( hasLauncher && type->canLaunch(itm) )
1031                     {
1032                         d->launchAsInfo[i] = qMakePair( type->id(), mode->id() );
1033                         auto* act = new QAction(menu);
1034                         act->setText( type->name() );
1035                         qCDebug(SHELL) << "Connect " << i << "for action" << act->text() << "in mode" << mode->name();
1036                         connect(act, &QAction::triggered,
1037                                 this, [this, i] () { Q_D(RunController); d->launchAs(i); } );
1038                         menu->addAction(act);
1039                         i++;
1040                     }
1041                 }
1042                 if( menu->menu()->actions().count() > 0 )
1043                 {
1044                     ext.addAction( ContextMenuExtension::RunGroup, menu);
1045                 } else {
1046                     delete menu;
1047                 }
1048             }
1049             if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 )
1050             {
1051                 d->contextItem = itm;
1052             }
1053         }
1054     }
1055     return ext;
1056 }
1057 
1058 
1059 
RunDelegate(QObject * parent)1060 RunDelegate::RunDelegate( QObject* parent )
1061 : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ),
1062 errorBrush( KColorScheme::View, KColorScheme::NegativeText )
1063 {
1064 }
1065 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const1066 void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
1067 {
1068     QStyleOptionViewItem opt = option;
1069 //     if( status.isValid() && status.canConvert<KDevelop::IRunProvider::OutputTypes>() )
1070 //     {
1071 //         IRunProvider::OutputTypes type = status.value<KDevelop::IRunProvider::OutputTypes>();
1072 //         if( type == IRunProvider::RunProvider )
1073 //         {
1074 //             opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) );
1075 //         } else if( type == IRunProvider::StandardError )
1076 //         {
1077 //             opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) );
1078 //         }
1079 //     }
1080     QItemDelegate::paint(painter, opt, index);
1081 }
1082 
1083 
1084 #include "moc_runcontroller.cpp"
1085