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"