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