nativeappconfig.cpp 15.6 KB
Newer Older
Andreas Pakulat's avatar
Andreas Pakulat committed
1 2
/*  This file is part of KDevelop
    Copyright 2009 Andreas Pakulat <apaku@gmx.de>
3
    Copyright 2010 Aleix Pol Gonzalez <aleixpol@kde.org>
Andreas Pakulat's avatar
Andreas Pakulat committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/
#include "nativeappconfig.h"

#include <interfaces/icore.h>
#include <interfaces/iprojectcontroller.h>
24
#include <interfaces/iruncontroller.h>
Andreas Pakulat's avatar
Andreas Pakulat committed
25 26 27 28 29
#include <interfaces/ilaunchconfiguration.h>

#include <project/projectmodel.h>

#include "nativeappjob.h"
30
#include <interfaces/iproject.h>
Milian Wolff's avatar
Milian Wolff committed
31
#include <project/interfaces/iprojectfilemanager.h>
32
#include <util/executecompositejob.h>
33

34 35 36
#include <interfaces/iplugincontroller.h>

#include "executeplugin.h"
Dāvis Mosāns's avatar
Dāvis Mosāns committed
37
#include "debug.h"
38
#include <util/kdevstringhandler.h>
39
#include "projecttargetscombobox.h"
40 41 42

#include <QIcon>
#include <QMenu>
Andreas Pakulat's avatar
Andreas Pakulat committed
43

Kevin Funk's avatar
Kevin Funk committed
44
#include <KConfigGroup>
Kevin Funk's avatar
Kevin Funk committed
45
#include <KLineEdit>
46
#include <KLocalizedString>
47
#include <KShell>
48

49

50 51
using namespace KDevelop;

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
52
QIcon NativeAppConfigPage::icon() const
Andreas Pakulat's avatar
Andreas Pakulat committed
53
{
54
    return QIcon::fromTheme(QStringLiteral("system-run"));
Andreas Pakulat's avatar
Andreas Pakulat committed
55 56
}

57 58 59 60 61
static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model)
{
    return model->itemFromIndex(model->pathToIndex(path));
}

Andreas Pakulat's avatar
Andreas Pakulat committed
62 63
//TODO: Make sure to auto-add the executable target to the dependencies when its used.

Dāvis Mosāns's avatar
Dāvis Mosāns committed
64
void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project )
Andreas Pakulat's avatar
Andreas Pakulat committed
65
{
66
    QSignalBlocker blocker(this);
67
    projectTarget->setBaseItem( project ? project->projectItem() : nullptr, true);
68
    projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) );
69

Milian Wolff's avatar
Milian Wolff committed
70
    QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl());
71
    if( !exe.isEmpty() || project ){
72
        executablePath->setUrl( !exe.isEmpty() ? exe : project->path().toUrl() );
73 74 75
    }else{
        KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController();
        if( pc ){
76
            executablePath->setUrl( pc->projects().isEmpty() ? QUrl() : pc->projects().at(0)->path().toUrl() );
77 78
        }
    }
79
    dependencies->setSuggestion(project);
80 81 82 83 84

    //executablePath->setFilter("application/x-executable");

    executableRadio->setChecked( true );
    if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){
Andreas Pakulat's avatar
Andreas Pakulat committed
85 86
        projectTargetRadio->setChecked( true );
    }
87

Kevin Funk's avatar
Kevin Funk committed
88
    arguments->setClearButtonEnabled( true );
Andreas Pakulat's avatar
Andreas Pakulat committed
89
    arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) );
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
90
    workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) );
91
    environment->setCurrentProfile(cfg.readEntry(ExecutePlugin::environmentProfileEntry, QString()));
92 93
    runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) );
    terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) );
94 95
    dependencies->setDependencies(KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList());

Andreas Pakulat's avatar
Andreas Pakulat committed
96 97 98
    dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) );
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
99
NativeAppConfigPage::NativeAppConfigPage( QWidget* parent )
Andreas Pakulat's avatar
Andreas Pakulat committed
100 101
    : LaunchConfigurationPage( parent )
{
Dāvis Mosāns's avatar
Dāvis Mosāns committed
102
    setupUi(this);
Andreas Pakulat's avatar
Andreas Pakulat committed
103
    //Setup data info for combobox
104 105 106 107
    dependencyAction->setItemData(0, QStringLiteral("Nothing"));
    dependencyAction->setItemData(1, QStringLiteral("Build"));
    dependencyAction->setItemData(2, QStringLiteral("Install"));
    dependencyAction->setItemData(3, QStringLiteral("SudoInstall"));
108

109 110 111
    //Set workingdirectory widget to ask for directories rather than files
    workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly);

112
    configureEnvironment->setSelectionWidget(environment);
113

Andreas Pakulat's avatar
Andreas Pakulat committed
114
    //connect signals to changed signal
115
    connect( projectTarget, &QComboBox::currentTextChanged, this, &NativeAppConfigPage::changed );
116 117 118 119 120 121 122
    connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed );
    connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed );
    connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed );
    connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed );
    connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed );
    connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed );
    connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed );
123
    connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed );
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
124
    connect( dependencyAction, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed );
125 126
    connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed );
    connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed );
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
127 128
    connect( terminal, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed );
    connect( dependencyAction, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps );
129
    connect( dependencies, &DependenciesWidget::changed, this, &NativeAppConfigPage::changed );
130 131 132 133
}

void NativeAppConfigPage::activateDeps( int idx )
{
134
    dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != QLatin1String("Nothing") );
Andreas Pakulat's avatar
Andreas Pakulat committed
135 136
}

137
void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const
Andreas Pakulat's avatar
Andreas Pakulat committed
138
{
139
    Q_UNUSED( project );
Andreas Pakulat's avatar
Andreas Pakulat committed
140
    cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() );
141 142
    cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() );
    cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() );
Andreas Pakulat's avatar
Andreas Pakulat committed
143 144
    cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() );
    cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() );
145
    cfg.writeEntry( ExecutePlugin::environmentProfileEntry, environment->currentProfile() );
146 147
    cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() );
    cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() );
Andreas Pakulat's avatar
Andreas Pakulat committed
148
    cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() );
149
    QVariantList deps = dependencies->dependencies();
150
    cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) );
Andreas Pakulat's avatar
Andreas Pakulat committed
151 152
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
153
QString NativeAppConfigPage::title() const
Andreas Pakulat's avatar
Andreas Pakulat committed
154 155 156 157
{
    return i18n("Configure Native Application");
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
158
QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const
Andreas Pakulat's avatar
Andreas Pakulat committed
159 160 161 162 163 164
{
    return QList<KDevelop::LaunchConfigurationPageFactory*>();
}

QString NativeAppLauncher::description() const
{
165
    return i18n("Executes Native Applications");
Andreas Pakulat's avatar
Andreas Pakulat committed
166 167
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
168
QString NativeAppLauncher::id()
Andreas Pakulat's avatar
Andreas Pakulat committed
169
{
170
    return QStringLiteral("nativeAppLauncher");
Andreas Pakulat's avatar
Andreas Pakulat committed
171 172
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
173
QString NativeAppLauncher::name() const
Andreas Pakulat's avatar
Andreas Pakulat committed
174 175 176 177 178 179 180 181 182 183 184 185 186
{
    return i18n("Native Application");
}

NativeAppLauncher::NativeAppLauncher()
{
}

KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg)
{
    Q_ASSERT(cfg);
    if( !cfg )
    {
187
        return nullptr;
Andreas Pakulat's avatar
Andreas Pakulat committed
188
    }
189
    if( launchMode == QLatin1String("execute") )
Andreas Pakulat's avatar
Andreas Pakulat committed
190
    {
191
        auto* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension<IExecutePlugin>();
192
        Q_ASSERT(iface);
193
        KJob* depjob = iface->dependencyJob( cfg );
194 195
        QList<KJob*> l;
        if( depjob )
196
        {
197
            l << depjob;
198
        }
199 200
        l << new NativeAppJob( KDevelop::ICore::self()->runController(), cfg );
        return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l );
Dāvis Mosāns's avatar
Dāvis Mosāns committed
201

Andreas Pakulat's avatar
Andreas Pakulat committed
202
    }
203
    qCWarning(PLUGIN_EXECUTE) << "Unknown launch mode " << launchMode << "for config:" << cfg->name();
204
    return nullptr;
Andreas Pakulat's avatar
Andreas Pakulat committed
205 206 207 208
}

QStringList NativeAppLauncher::supportedModes() const
{
209
    return QStringList() << QStringLiteral("execute");
Andreas Pakulat's avatar
Andreas Pakulat committed
210 211 212 213 214 215 216 217 218 219 220
}

KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent)
{
    return new NativeAppConfigPage( parent );
}

NativeAppPageFactory::NativeAppPageFactory()
{
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
221
NativeAppConfigType::NativeAppConfigType()
Andreas Pakulat's avatar
Andreas Pakulat committed
222 223 224 225
{
    factoryList.append( new NativeAppPageFactory() );
}

Milian Wolff's avatar
Milian Wolff committed
226 227 228 229 230 231
NativeAppConfigType::~NativeAppConfigType()
{
    qDeleteAll(factoryList);
    factoryList.clear();
}

Andreas Pakulat's avatar
Andreas Pakulat committed
232 233
QString NativeAppConfigType::name() const
{
234
    return i18n("Compiled Binary");
Andreas Pakulat's avatar
Andreas Pakulat committed
235 236 237
}


Dāvis Mosāns's avatar
Dāvis Mosāns committed
238
QList<KDevelop::LaunchConfigurationPageFactory*> NativeAppConfigType::configPages() const
Andreas Pakulat's avatar
Andreas Pakulat committed
239 240 241 242
{
    return factoryList;
}

Dāvis Mosāns's avatar
Dāvis Mosāns committed
243
QString NativeAppConfigType::id() const
Andreas Pakulat's avatar
Andreas Pakulat committed
244
{
245
    return ExecutePlugin::_nativeAppConfigTypeId;
Andreas Pakulat's avatar
Andreas Pakulat committed
246 247
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
248
QIcon NativeAppConfigType::icon() const
Andreas Pakulat's avatar
Andreas Pakulat committed
249
{
250
    return QIcon::fromTheme(QStringLiteral("application-x-executable"));
Andreas Pakulat's avatar
Andreas Pakulat committed
251 252
}

253 254 255 256 257 258 259
bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const
{
    if( item->target() && item->target()->executable() ) {
        return canLaunch( item->target()->executable()->builtUrl() );
    }
    return false;
}
Andreas Pakulat's avatar
Andreas Pakulat committed
260

Milian Wolff's avatar
Milian Wolff committed
261
bool NativeAppConfigType::canLaunch ( const QUrl& file ) const
262 263 264
{
    return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() );
}
Andreas Pakulat's avatar
Andreas Pakulat committed
265

266 267 268 269 270
void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const
{
    cfg.writeEntry( ExecutePlugin::isExecutableEntry, false );
    KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
    cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) );
Milian Wolff's avatar
Milian Wolff committed
271
    cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) );
272 273 274
    cfg.sync();
}

275 276 277
void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const
{
    cfg.writeEntry( ExecutePlugin::isExecutableEntry, true );
278 279
//  TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore
    cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) );
280 281
    QStringList a(args);
    a.removeFirst();
282
    cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) );
283 284 285
    cfg.sync();
}

286 287 288
QList<KDevelop::ProjectTargetItem*> targetsInFolder(KDevelop::ProjectFolderItem* folder)
{
    QList<KDevelop::ProjectTargetItem*> ret;
289 290
    const auto folders = folder->folderList();
    for (KDevelop::ProjectFolderItem* f : folders) {
291
        ret += targetsInFolder(f);
292
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
293

294 295 296 297
    ret += folder->targetList();
    return ret;
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
298 299 300 301 302 303 304 305 306 307
bool actionLess(QAction* a, QAction* b)
{
    return a->text() < b->text();
}

bool menuLess(QMenu* a, QMenu* b)
{
    return a->title() < b->title();
}

308
QMenu* NativeAppConfigType::launcherSuggestions()
309
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
310
    QMenu* ret = new QMenu(i18n("Project Executables"));
Dāvis Mosāns's avatar
Dāvis Mosāns committed
311

312
    KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
313
    const QList<KDevelop::IProject*> projects = KDevelop::ICore::self()->projectController()->projects();
Dāvis Mosāns's avatar
Dāvis Mosāns committed
314

315
    for (KDevelop::IProject* project : projects) {
316
        if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) {
317
            const QList<KDevelop::ProjectTargetItem*> targets = targetsInFolder(project->projectItem());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
318
            QHash<KDevelop::ProjectBaseItem*, QList<QAction*> > targetsContainer;
319
            QMenu* projectMenu = ret->addMenu(QIcon::fromTheme(QStringLiteral("project-development")), project->name());
320
            for (KDevelop::ProjectTargetItem* target : targets) {
321
                if(target->executable()) {
322
                    QStringList path = model->pathFromIndex(target->index());
323
                    if(!path.isEmpty()){
324
                        auto* act = new QAction(projectMenu);
325
                        act->setData(KDevelop::joinWithEscaping(path, QLatin1Char('/'), QLatin1Char('\\')));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
326
                        act->setProperty("name", target->text());
327
                        path.removeFirst();
328
                        act->setText(path.join(QLatin1Char('/')));
329
                        act->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
330
                        connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
331
                        targetsContainer[target->parent()].append(act);
332
                    }
333 334
                }
            }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
335 336 337

            QList<QAction*> separateActions;
            QList<QMenu*> submenus;
338 339 340
            for (auto it = targetsContainer.constBegin(), end = targetsContainer.constEnd(); it != end; ++it) {
                KDevelop::ProjectBaseItem* folder = it.key();
                QList<QAction*> actions = it.value();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
341
                if(actions.size()==1 || !folder->parent()) {
342
                    separateActions.append(actions);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
343
                } else {
344
                    for (QAction* a : qAsConst(actions)) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
345 346 347 348
                        a->setText(a->property("name").toString());
                    }
                    QStringList path = model->pathFromIndex(folder->index());
                    path.removeFirst();
349
                    QMenu* submenu = new QMenu(path.join(QLatin1Char('/')), projectMenu);
350
                    std::sort(actions.begin(), actions.end(), actionLess);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
351 352 353 354
                    submenu->addActions(actions);
                    submenus += submenu;
                }
            }
Kevin Funk's avatar
Kevin Funk committed
355 356
            std::sort(separateActions.begin(), separateActions.end(), actionLess);
            std::sort(submenus.begin(), submenus.end(), menuLess);
357
            for (QMenu* m : qAsConst(submenus)) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
358
                projectMenu->addMenu(m);
359
            }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
360 361
            projectMenu->addActions(separateActions);

362
            projectMenu->setEnabled(!projectMenu->isEmpty());
363 364
        }
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
365

366 367 368 369 370
    return ret;
}

void NativeAppConfigType::suggestionTriggered()
{
371
    auto* action = qobject_cast<QAction*>(sender());
372
    KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel();
373
    KDevelop::ProjectTargetItem* pitem = dynamic_cast<KDevelop::ProjectTargetItem*>(itemForPath(KDevelop::splitWithEscaping(action->data().toString(), QLatin1Char('/'), QLatin1Char('\\')), model));
374 375 376
    if(pitem) {
        QPair<QString,QString> launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() );
        KDevelop::IProject* p = pitem->project();
Dāvis Mosāns's avatar
Dāvis Mosāns committed
377

378 379
        KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text());
        KConfigGroup cfg = config->config();
Dāvis Mosāns's avatar
Dāvis Mosāns committed
380

381 382 383 384
        QStringList splitPath = model->pathFromIndex(pitem->index());
//         QString path = KDevelop::joinWithEscaping(splitPath,'/','\\');
        cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath );
        cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) );
385 386
        cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" );
        cfg.sync();
Dāvis Mosāns's avatar
Dāvis Mosāns committed
387

388 389 390
        emit signalAddLaunchConfiguration(config);
    }
}
391