File indexing completed on 2024-04-28 04:38:39
0001 /* 0002 SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de> 0003 SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "nativeappconfig.h" 0009 0010 #include <interfaces/icore.h> 0011 #include <interfaces/iprojectcontroller.h> 0012 #include <interfaces/iruncontroller.h> 0013 #include <interfaces/ilaunchconfiguration.h> 0014 0015 #include <project/projectmodel.h> 0016 0017 #include "nativeappjob.h" 0018 #include <interfaces/iproject.h> 0019 #include <project/interfaces/iprojectfilemanager.h> 0020 #include <util/executecompositejob.h> 0021 0022 #include <interfaces/iplugincontroller.h> 0023 0024 #include "executeplugin.h" 0025 #include "debug.h" 0026 #include <util/kdevstringhandler.h> 0027 #include "projecttargetscombobox.h" 0028 0029 #include <QIcon> 0030 #include <QMenu> 0031 0032 #include <KConfigGroup> 0033 #include <KLineEdit> 0034 #include <KLocalizedString> 0035 #include <KShell> 0036 0037 0038 using namespace KDevelop; 0039 0040 QIcon NativeAppConfigPage::icon() const 0041 { 0042 return QIcon::fromTheme(QStringLiteral("system-run")); 0043 } 0044 0045 static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model) 0046 { 0047 return model->itemFromIndex(model->pathToIndex(path)); 0048 } 0049 0050 //TODO: Make sure to auto-add the executable target to the dependencies when its used. 0051 0052 void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) 0053 { 0054 QSignalBlocker blocker(this); 0055 projectTarget->setBaseItem( project ? project->projectItem() : nullptr, true); 0056 projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) ); 0057 0058 QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl()); 0059 if( !exe.isEmpty() || project ){ 0060 executablePath->setUrl( !exe.isEmpty() ? exe : project->path().toUrl() ); 0061 }else{ 0062 KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController(); 0063 if( pc ){ 0064 executablePath->setUrl( pc->projects().isEmpty() ? QUrl() : pc->projects().at(0)->path().toUrl() ); 0065 } 0066 } 0067 dependencies->setSuggestion(project); 0068 0069 //executablePath->setFilter("application/x-executable"); 0070 0071 executableRadio->setChecked( true ); 0072 if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){ 0073 projectTargetRadio->setChecked( true ); 0074 } 0075 0076 arguments->setClearButtonEnabled( true ); 0077 arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) ); 0078 workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) ); 0079 environment->setCurrentProfile(cfg.readEntry(ExecutePlugin::environmentProfileEntry, QString())); 0080 runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) ); 0081 terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) ); 0082 dependencies->setDependencies(KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList()); 0083 0084 dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) ); 0085 0086 if (cfg.readEntry<bool>(ExecutePlugin::configuredByCTest, false)) { 0087 killBeforeStartingAgain->setCurrentIndex(killBeforeStartingAgain->findData(NativeAppJob::startAnother)); 0088 killBeforeStartingAgain->setDisabled(true); 0089 } else { 0090 killBeforeStartingAgain->setCurrentIndex(killBeforeStartingAgain->findData(cfg.readEntry<int>(ExecutePlugin::killBeforeExecutingAgain, NativeAppJob::askIfRunning))); 0091 } 0092 } 0093 0094 NativeAppConfigPage::NativeAppConfigPage( QWidget* parent ) 0095 : LaunchConfigurationPage( parent ) 0096 { 0097 setupUi(this); 0098 //Setup data info for combobox 0099 dependencyAction->setItemData(0, QStringLiteral("Nothing")); 0100 dependencyAction->setItemData(1, QStringLiteral("Build")); 0101 dependencyAction->setItemData(2, QStringLiteral("Install")); 0102 dependencyAction->setItemData(3, QStringLiteral("SudoInstall")); 0103 0104 killBeforeStartingAgain->addItem(i18nc("@item:inlistbox", "Ask If Running"), NativeAppJob::askIfRunning); 0105 killBeforeStartingAgain->addItem(i18nc("@item:inlistbox", "Kill All Instances"), NativeAppJob::killAllInstances); 0106 killBeforeStartingAgain->addItem(i18nc("@item:inlistbox", "Start Another"), NativeAppJob::startAnother); 0107 0108 //Set workingdirectory widget to ask for directories rather than files 0109 workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); 0110 0111 configureEnvironment->setSelectionWidget(environment); 0112 0113 //connect signals to changed signal 0114 connect( projectTarget, &QComboBox::currentTextChanged, this, &NativeAppConfigPage::changed ); 0115 connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); 0116 connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); 0117 connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); 0118 connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); 0119 connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed ); 0120 connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); 0121 connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); 0122 connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed ); 0123 connect( dependencyAction, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); 0124 connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed ); 0125 connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed ); 0126 connect( terminal, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); 0127 connect( dependencyAction, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps ); 0128 connect( killBeforeStartingAgain, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); 0129 connect( dependencies, &DependenciesWidget::changed, this, &NativeAppConfigPage::changed ); 0130 } 0131 0132 void NativeAppConfigPage::activateDeps( int idx ) 0133 { 0134 dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") ); 0135 } 0136 0137 void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const 0138 { 0139 Q_UNUSED( project ); 0140 cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() ); 0141 cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() ); 0142 cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() ); 0143 cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() ); 0144 cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() ); 0145 cfg.writeEntry( ExecutePlugin::environmentProfileEntry, environment->currentProfile() ); 0146 cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() ); 0147 cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() ); 0148 cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() ); 0149 cfg.writeEntry( ExecutePlugin::killBeforeExecutingAgain, killBeforeStartingAgain->itemData( killBeforeStartingAgain->currentIndex() ).toInt() ); 0150 QVariantList deps = dependencies->dependencies(); 0151 cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) ); 0152 } 0153 0154 QString NativeAppConfigPage::title() const 0155 { 0156 return i18nc("@title:tab", "Configure Native Application"); 0157 } 0158 0159 QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const 0160 { 0161 return QList<KDevelop::LaunchConfigurationPageFactory*>(); 0162 } 0163 0164 QString NativeAppLauncher::description() const 0165 { 0166 return i18n("Executes Native Applications"); 0167 } 0168 0169 QString NativeAppLauncher::id() 0170 { 0171 return QStringLiteral("nativeAppLauncher"); 0172 } 0173 0174 QString NativeAppLauncher::name() const 0175 { 0176 return i18n("Native Application"); 0177 } 0178 0179 NativeAppLauncher::NativeAppLauncher() 0180 { 0181 } 0182 0183 KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) 0184 { 0185 Q_ASSERT(cfg); 0186 if( !cfg ) 0187 { 0188 return nullptr; 0189 } 0190 if( launchMode == QLatin1String("execute") ) 0191 { 0192 auto* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension<IExecutePlugin>(); 0193 Q_ASSERT(iface); 0194 KJob* depjob = iface->dependencyJob( cfg ); 0195 QList<KJob*> l; 0196 if( depjob ) 0197 { 0198 l << depjob; 0199 } 0200 auto nativeAppJob = new NativeAppJob( KDevelop::ICore::self()->runController(), cfg ); 0201 QObject::connect(nativeAppJob, &NativeAppJob::killBeforeExecutingAgainChanged, KDevelop::ICore::self()->runController(), [cfg] (int newValue) { 0202 auto cfgGroup = cfg->config(); 0203 cfgGroup.writeEntry(ExecutePlugin::killBeforeExecutingAgain, newValue); 0204 }); 0205 l << nativeAppJob; 0206 0207 return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); 0208 0209 } 0210 qCWarning(PLUGIN_EXECUTE) << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); 0211 return nullptr; 0212 } 0213 0214 QStringList NativeAppLauncher::supportedModes() const 0215 { 0216 return QStringList() << QStringLiteral("execute"); 0217 } 0218 0219 KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent) 0220 { 0221 return new NativeAppConfigPage( parent ); 0222 } 0223 0224 NativeAppPageFactory::NativeAppPageFactory() 0225 { 0226 } 0227 0228 NativeAppConfigType::NativeAppConfigType() 0229 { 0230 factoryList.append( new NativeAppPageFactory() ); 0231 } 0232 0233 NativeAppConfigType::~NativeAppConfigType() 0234 { 0235 qDeleteAll(factoryList); 0236 factoryList.clear(); 0237 } 0238 0239 QString NativeAppConfigType::name() const 0240 { 0241 return i18n("Compiled Binary"); 0242 } 0243 0244 0245 QList<KDevelop::LaunchConfigurationPageFactory*> NativeAppConfigType::configPages() const 0246 { 0247 return factoryList; 0248 } 0249 0250 QString NativeAppConfigType::sharedId() 0251 { 0252 return QStringLiteral("Native Application"); 0253 } 0254 0255 QString NativeAppConfigType::id() const 0256 { 0257 return sharedId(); 0258 } 0259 0260 QIcon NativeAppConfigType::icon() const 0261 { 0262 return QIcon::fromTheme(QStringLiteral("application-x-executable")); 0263 } 0264 0265 bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const 0266 { 0267 if( item->target() && item->target()->executable() ) { 0268 return canLaunch( item->target()->executable()->builtUrl() ); 0269 } 0270 return false; 0271 } 0272 0273 bool NativeAppConfigType::canLaunch ( const QUrl& file ) const 0274 { 0275 return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() ); 0276 } 0277 0278 void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const 0279 { 0280 cfg.writeEntry( ExecutePlugin::isExecutableEntry, false ); 0281 KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); 0282 cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) ); 0283 cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) ); 0284 cfg.sync(); 0285 } 0286 0287 void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const 0288 { 0289 cfg.writeEntry( ExecutePlugin::isExecutableEntry, true ); 0290 // TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore 0291 cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) ); 0292 QStringList a(args); 0293 a.removeFirst(); 0294 cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) ); 0295 cfg.sync(); 0296 } 0297 0298 QList<KDevelop::ProjectTargetItem*> targetsInFolder(KDevelop::ProjectFolderItem* folder) 0299 { 0300 QList<KDevelop::ProjectTargetItem*> ret; 0301 const auto folders = folder->folderList(); 0302 for (KDevelop::ProjectFolderItem* f : folders) { 0303 ret += targetsInFolder(f); 0304 } 0305 0306 ret += folder->targetList(); 0307 return ret; 0308 } 0309 0310 bool actionLess(QAction* a, QAction* b) 0311 { 0312 return a->text() < b->text(); 0313 } 0314 0315 bool menuLess(QMenu* a, QMenu* b) 0316 { 0317 return a->title() < b->title(); 0318 } 0319 0320 QMenu* NativeAppConfigType::launcherSuggestions() 0321 { 0322 auto* ret = new QMenu(i18nc("@title:menu", "Project Executables")); 0323 0324 KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); 0325 const QList<KDevelop::IProject*> projects = KDevelop::ICore::self()->projectController()->projects(); 0326 0327 for (KDevelop::IProject* project : projects) { 0328 if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) { 0329 const QList<KDevelop::ProjectTargetItem*> targets = targetsInFolder(project->projectItem()); 0330 QHash<KDevelop::ProjectBaseItem*, QList<QAction*> > targetsContainer; 0331 QMenu* projectMenu = ret->addMenu(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); 0332 for (KDevelop::ProjectTargetItem* target : targets) { 0333 if(target->executable()) { 0334 QStringList path = model->pathFromIndex(target->index()); 0335 if(!path.isEmpty()){ 0336 auto* act = new QAction(projectMenu); 0337 act->setData(KDevelop::joinWithEscaping(path, QLatin1Char('/'), QLatin1Char('\\'))); 0338 act->setProperty("name", target->text()); 0339 path.removeFirst(); 0340 act->setText(path.join(QLatin1Char('/'))); 0341 act->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); 0342 connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered); 0343 targetsContainer[target->parent()].append(act); 0344 } 0345 } 0346 } 0347 0348 QList<QAction*> separateActions; 0349 QList<QMenu*> submenus; 0350 for (auto it = targetsContainer.constBegin(), end = targetsContainer.constEnd(); it != end; ++it) { 0351 KDevelop::ProjectBaseItem* folder = it.key(); 0352 QList<QAction*> actions = it.value(); 0353 if(actions.size()==1 || !folder->parent()) { 0354 separateActions.append(actions); 0355 } else { 0356 for (QAction* a : qAsConst(actions)) { 0357 a->setText(a->property("name").toString()); 0358 } 0359 QStringList path = model->pathFromIndex(folder->index()); 0360 path.removeFirst(); 0361 auto* submenu = new QMenu(path.join(QLatin1Char('/')), projectMenu); 0362 std::sort(actions.begin(), actions.end(), actionLess); 0363 submenu->addActions(actions); 0364 submenus += submenu; 0365 } 0366 } 0367 std::sort(separateActions.begin(), separateActions.end(), actionLess); 0368 std::sort(submenus.begin(), submenus.end(), menuLess); 0369 for (QMenu* m : qAsConst(submenus)) { 0370 projectMenu->addMenu(m); 0371 } 0372 projectMenu->addActions(separateActions); 0373 0374 projectMenu->setEnabled(!projectMenu->isEmpty()); 0375 } 0376 } 0377 0378 return ret; 0379 } 0380 0381 void NativeAppConfigType::suggestionTriggered() 0382 { 0383 auto* action = qobject_cast<QAction*>(sender()); 0384 KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); 0385 KDevelop::ProjectTargetItem* pitem = dynamic_cast<KDevelop::ProjectTargetItem*>(itemForPath(KDevelop::splitWithEscaping(action->data().toString(), QLatin1Char('/'), QLatin1Char('\\')), model)); 0386 if(pitem) { 0387 QPair<QString,QString> launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() ); 0388 KDevelop::IProject* p = pitem->project(); 0389 0390 KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text()); 0391 KConfigGroup cfg = config->config(); 0392 0393 QStringList splitPath = model->pathFromIndex(pitem->index()); 0394 // QString path = KDevelop::joinWithEscaping(splitPath,'/','\\'); 0395 cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath ); 0396 cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) ); 0397 cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" ); 0398 cfg.sync(); 0399 0400 emit signalAddLaunchConfiguration(config); 0401 } 0402 } 0403 0404 #include "moc_nativeappconfig.cpp"