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"