1 /*
2     SPDX-FileCopyrightText: 2009 Andreas Pakulat <apaku@gmx.de>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "launchconfigurationdialog.h"
8 
9 #include <QDialogButtonBox>
10 #include <QLabel>
11 #include <QMenu>
12 #include <QPushButton>
13 #include <QTabWidget>
14 #include <QTreeView>
15 #include <QVBoxLayout>
16 
17 #include <KComboBox>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 
21 #include <interfaces/launchconfigurationpage.h>
22 #include <interfaces/iproject.h>
23 #include <interfaces/iprojectcontroller.h>
24 #include <interfaces/isession.h>
25 
26 #include "core.h"
27 #include "runcontroller.h"
28 #include "launchconfiguration.h"
29 #include "debug.h"
30 
31 #include <interfaces/ilauncher.h>
32 #include <interfaces/ilaunchmode.h>
33 #include <interfaces/launchconfigurationtype.h>
34 
35 namespace KDevelop
36 {
37 
launchConfigGreaterThan(KDevelop::LaunchConfigurationType * a,KDevelop::LaunchConfigurationType * b)38 bool launchConfigGreaterThan(KDevelop::LaunchConfigurationType* a, KDevelop::LaunchConfigurationType* b)
39 {
40     return a->name()>b->name();
41 }
42 
43 //TODO: Maybe use KPageDialog instead, might make the model stuff easier and the default-size stuff as well
LaunchConfigurationDialog(QWidget * parent)44 LaunchConfigurationDialog::LaunchConfigurationDialog(QWidget* parent)
45     : QDialog(parent)
46 {
47     setWindowTitle( i18nc("@title:window", "Launch Configurations" ) );
48 
49     auto* mainWidget = new QWidget(this);
50     auto *mainLayout = new QVBoxLayout(this);
51     mainLayout->addWidget(mainWidget);
52 
53     setupUi(mainWidget);
54     splitter->setSizes(QList<int>{260, 620});
55     splitter->setCollapsible(0, false);
56 
57     addConfig->setToolTip(i18nc("@info:tooltip", "Add a new launch configuration."));
58     deleteConfig->setEnabled( false );
59     deleteConfig->setToolTip(i18nc("@info:tooltip", "Delete selected launch configuration."));
60 
61     model = new LaunchConfigurationsModel( tree );
62     tree->setModel( model );
63     tree->setExpandsOnDoubleClick( true );
64     tree->setSelectionBehavior( QAbstractItemView::SelectRows );
65     tree->setSelectionMode( QAbstractItemView::SingleSelection );
66     tree->setUniformRowHeights( true );
67     tree->setItemDelegate( new LaunchConfigurationModelDelegate(this) );
68     tree->setColumnHidden(1, true);
69     for(int row=0; row<model->rowCount(); row++) {
70         tree->setExpanded(model->index(row, 0), true);
71     }
72 
73     tree->setContextMenuPolicy(Qt::CustomContextMenu);
74     connect( tree, &QTreeView::customContextMenuRequested, this, &LaunchConfigurationDialog::doTreeContextMenu );
75     connect( deleteConfig, &QPushButton::clicked, this, &LaunchConfigurationDialog::deleteConfiguration);
76     connect( model, &LaunchConfigurationsModel::dataChanged, this, &LaunchConfigurationDialog::modelChanged );
77     connect( tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &LaunchConfigurationDialog::selectionChanged);
78     QModelIndex idx = model->indexForConfig( Core::self()->runControllerInternal()->defaultLaunch() );
79     qCDebug(SHELL) << "selecting index:" << idx;
80     if( !idx.isValid() )
81     {
82         for( int i = 0; i < model->rowCount(); i++ )
83         {
84             if( model->rowCount( model->index( i, 0, QModelIndex() ) ) > 0 )
85             {
86                 idx = model->index( 1, 0, model->index( i, 0, QModelIndex() ) );
87                 break;
88             }
89         }
90         if( !idx.isValid() )
91         {
92             idx = model->index( 0, 0, QModelIndex() );
93         }
94     }
95     tree->selectionModel()->select( QItemSelection( idx, idx ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
96     tree->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
97 
98     // Unfortunately tree->resizeColumnToContents() only looks at the top-level
99     // items, instead of all open ones. Hence we're calculating it ourselves like
100     // this:
101     // Take the selected index, check if it has childs, if so take the first child
102     // Then count the level by going up, then let the tree calculate the width
103     // for the selected or its first child index and add indentation*level
104     //
105     // If Qt Software ever fixes resizeColumnToContents, the following line
106     // can be enabled and the rest be removed
107     // tree->resizeColumnToContents( 0 );
108     int level = 0;
109     QModelIndex widthidx = idx;
110     if( model->rowCount( idx ) > 0 )
111     {
112         widthidx = model->index( 0, 0, idx );
113     }
114     QModelIndex parentidx = widthidx.parent();
115     while( parentidx.isValid() )
116     {
117         level++;
118         parentidx = parentidx.parent();
119     }
120     // make sure the base column width is honored, e.g. when no launch configs exist
121     tree->resizeColumnToContents(0);
122     int width = tree->columnWidth( 0 );
123     while ( widthidx.isValid() )
124     {
125         width = qMax( width, level*tree->indentation() + tree->indentation() + tree->sizeHintForIndex( widthidx ).width() );
126         widthidx = widthidx.parent();
127     }
128     tree->setColumnWidth( 0, width );
129 
130     auto* m = new QMenu(this);
131     QList<LaunchConfigurationType*> types = Core::self()->runController()->launchConfigurationTypes();
132     std::sort(types.begin(), types.end(), launchConfigGreaterThan); //we want it in reverse order
133     for (LaunchConfigurationType* type : qAsConst(types)) {
134         connect(type, &LaunchConfigurationType::signalAddLaunchConfiguration, this, &LaunchConfigurationDialog::addConfiguration);
135         QMenu* suggestionsMenu = type->launcherSuggestions();
136 
137         if(suggestionsMenu) {
138             // take ownership
139             suggestionsMenu->setParent(m, suggestionsMenu->windowFlags());
140             m->addMenu(suggestionsMenu);
141         }
142     }
143     // Simplify menu structure to get rid of 1-entry levels
144     while (m->actions().count() == 1) {
145         QMenu* subMenu = m->actions().at(0)->menu();
146         if (subMenu && subMenu->isEnabled() && subMenu->actions().count()<5) {
147             m = subMenu;
148         } else {
149             break;
150         }
151     }
152     if(!m->isEmpty()) {
153         auto* separator = new QAction(m);
154         separator->setSeparator(true);
155         m->insertAction(m->actions().at(0), separator);
156     }
157 
158     for (LaunchConfigurationType* type : qAsConst(types)) {
159         auto* action = new QAction(type->icon(), type->name(), m);
160         action->setProperty("configtype", QVariant::fromValue<QObject*>(type));
161         connect(action, &QAction::triggered, this, &LaunchConfigurationDialog::createEmptyLauncher);
162 
163         if(!m->actions().isEmpty())
164             m->insertAction(m->actions().at(0), action);
165         else
166             m->addAction(action);
167     }
168     addConfig->setMenu(m);
169     addConfig->setEnabled( !m->isEmpty() );
170 
171     messageWidget->setCloseButtonVisible( false );
172     messageWidget->setMessageType( KMessageWidget::Warning );
173     messageWidget->setText( i18n("No launch configurations available. (Is any of the Execute plugins loaded?)") );
174     messageWidget->setVisible( m->isEmpty() );
175 
176     connect(debugger, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &LaunchConfigurationDialog::launchModeChanged);
177 
178     connect(buttonBox, &QDialogButtonBox::accepted, this, &LaunchConfigurationDialog::accept);
179     connect(buttonBox, &QDialogButtonBox::rejected, this, &LaunchConfigurationDialog::reject);
180     connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked,
181             this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig));
182     connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked,
183             this, QOverload<>::of(&LaunchConfigurationDialog::saveConfig));
184     mainLayout->addWidget(buttonBox);
185 
186     resize( QSize(qMax(1200, sizeHint().width()), qMax(500, sizeHint().height())) );
187 }
188 
doTreeContextMenu(const QPoint & point)189 void LaunchConfigurationDialog::doTreeContextMenu(const QPoint& point)
190 {
191     if ( ! tree->selectionModel()->selectedRows().isEmpty() ) {
192         QModelIndex selected = tree->selectionModel()->selectedRows().first();
193         if ( selected.parent().isValid() && ! selected.parent().parent().isValid() ) {
194             // only display the menu if a launch config is clicked
195             QMenu menu(tree);
196             auto* rename = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename Configuration"), &menu);
197             auto* delete_ = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete Configuration"), &menu);
198             connect(rename, &QAction::triggered, this, &LaunchConfigurationDialog::renameSelected);
199             connect(delete_, &QAction::triggered, this, &LaunchConfigurationDialog::deleteConfiguration);
200             menu.addAction(rename);
201             menu.addAction(delete_);
202             menu.exec(tree->viewport()->mapToGlobal(point));
203         }
204     }
205 }
206 
renameSelected()207 void LaunchConfigurationDialog::renameSelected()
208 {
209     if( !tree->selectionModel()->selectedRows().isEmpty() )
210     {
211         QModelIndex parent = tree->selectionModel()->selectedRows().first();
212         if( parent.parent().isValid() )
213         {
214             parent = parent.parent();
215         }
216         QModelIndex index = model->index(tree->selectionModel()->selectedRows().first().row(), 0, parent);
217         tree->edit( index );
218     }
219 }
220 
sizeHint() const221 QSize LaunchConfigurationDialog::sizeHint() const
222 {
223     QSize s = QDialog::sizeHint();
224     return s.expandedTo(QSize(880, 520));
225 }
226 
createEmptyLauncher()227 void LaunchConfigurationDialog::createEmptyLauncher()
228 {
229     auto* action = qobject_cast<QAction*>(sender());
230     Q_ASSERT(action);
231 
232     auto* type = qobject_cast<LaunchConfigurationType*>(action->property("configtype").value<QObject*>());
233     Q_ASSERT(type);
234 
235     IProject* p = model->projectForIndex(tree->currentIndex());
236     QPair< QString, QString > launcher( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() );
237     ILaunchConfiguration* l = ICore::self()->runController()->createLaunchConfiguration(type, launcher, p);
238     addConfiguration(l);
239 }
240 
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)241 void LaunchConfigurationDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
242 {
243     if( !deselected.indexes().isEmpty() )
244     {
245         LaunchConfiguration* l = model->configForIndex( deselected.indexes().first() );
246         if( l )
247         {
248             disconnect(l, &LaunchConfiguration::nameChanged, this,  &LaunchConfigurationDialog::updateNameLabel);
249             if( currentPageChanged )
250             {
251                 if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18nc("@title:window", "Unsaved Changes") ) == KMessageBox::Yes )
252                 {
253                     saveConfig( deselected.indexes().first() );
254                 } else {
255                     auto* tab = qobject_cast<LaunchConfigPagesContainer*>( stack->currentWidget() );
256                     tab->setLaunchConfiguration( l );
257                     buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false );
258                     currentPageChanged = false;
259                 }
260             }
261         }
262     }
263     updateNameLabel(nullptr);
264 
265     for( int i = 1; i < stack->count(); i++ )
266     {
267         QWidget* w = stack->widget(i);
268         stack->removeWidget(w);
269         delete w;
270     }
271 
272     if( !selected.indexes().isEmpty() )
273     {
274         QModelIndex idx = selected.indexes().first();
275         LaunchConfiguration* l = model->configForIndex( idx );
276         ILaunchMode* lm = model->modeForIndex( idx );
277 
278         if( l )
279         {
280             updateNameLabel( l );
281             tree->expand( model->indexForConfig( l ) );
282             connect( l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel );
283             if( lm )
284             {
285                 QVariant currentLaunchMode = idx.sibling(idx.row(), 1).data(Qt::EditRole);
286                 {
287                     QSignalBlocker blocker(debugger);
288                     const QList<ILauncher*> launchers = l->type()->launchers();
289 
290                     debugger->clear();
291                     for (ILauncher* launcher : launchers) {
292                         if (launcher->supportedModes().contains(lm->id())) {
293                             debugger->addItem(launcher->name(), launcher->id());
294                         }
295                     }
296 
297                     debugger->setCurrentIndex(debugger->findData(currentLaunchMode));
298                 }
299 
300                 debugger->setVisible(debugger->count()>0);
301                 debugLabel->setVisible(debugger->count()>0);
302 
303                 ILauncher* launcher = l->type()->launcherForId( currentLaunchMode.toString() );
304                 if( launcher )
305                 {
306                     LaunchConfigPagesContainer* tab = launcherWidgets.value( launcher );
307                     if(!tab)
308                     {
309                         QList<KDevelop::LaunchConfigurationPageFactory*> pages = launcher->configPages();
310                         if(!pages.isEmpty()) {
311                             tab = new LaunchConfigPagesContainer( launcher->configPages(), stack );
312                             connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged );
313                             stack->addWidget( tab );
314                         }
315                     }
316 
317                     if(tab) {
318                         tab->setLaunchConfiguration( l );
319                         stack->setCurrentWidget( tab );
320                     } else {
321                         auto* label = new QLabel(i18nc("%1 is a launcher name",
322                                                        "No configuration is needed for '%1'",
323                                                        launcher->name()), stack);
324                         label->setAlignment(Qt::AlignCenter);
325                         QFont font = label->font();
326                         font.setItalic(true);
327                         label->setFont(font);
328                         stack->addWidget(label);
329                         stack->setCurrentWidget(label);
330                     }
331 
332                     updateNameLabel( l );
333                     addConfig->setEnabled( false );
334                     deleteConfig->setEnabled( false );
335                 } else
336                 {
337                     addConfig->setEnabled( false );
338                     deleteConfig->setEnabled( false );
339                     stack->setCurrentIndex( 0 );
340                 }
341             } else
342             {
343                 //TODO: enable removal button
344                 LaunchConfigurationType* type = l->type();
345                 LaunchConfigPagesContainer* tab = typeWidgets.value( type );
346                 if( !tab )
347                 {
348                     tab = new LaunchConfigPagesContainer( type->configPages(), stack );
349                     connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged );
350                     stack->addWidget( tab );
351                 }
352                 qCDebug(SHELL) << "created pages, setting config up";
353                 tab->setLaunchConfiguration( l );
354                 stack->setCurrentWidget( tab );
355 
356                 addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() );
357                 deleteConfig->setEnabled( true );
358                 debugger->setVisible( false );
359                 debugLabel->setVisible( false );
360             }
361         } else
362         {
363             addConfig->setEnabled( addConfig->menu() && !addConfig->menu()->isEmpty() );
364             deleteConfig->setEnabled( false );
365             stack->setCurrentIndex( 0 );
366             auto* l = new QLabel(i18n("<i>Select a configuration to edit from the left,<br>"
367                                       "or click the \"Add\" button to add a new one.</i>"), stack);
368             l->setAlignment(Qt::AlignCenter);
369             stack->addWidget(l);
370             stack->setCurrentWidget(l);
371             debugger->setVisible( false );
372             debugLabel->setVisible( false );
373         }
374     } else
375     {
376         debugger->setVisible( false );
377         debugLabel->setVisible( false );
378         addConfig->setEnabled( false );
379         deleteConfig->setEnabled( false );
380         stack->setCurrentIndex( 0 );
381     }
382 }
383 
saveConfig(const QModelIndex & idx)384 void LaunchConfigurationDialog::saveConfig( const QModelIndex& idx )
385 {
386     Q_UNUSED( idx );
387     auto* tab = qobject_cast<LaunchConfigPagesContainer*>( stack->currentWidget() );
388     if( tab )
389     {
390         tab->save();
391         buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false );
392         currentPageChanged = false;
393     }
394 }
395 
saveConfig()396 void LaunchConfigurationDialog::saveConfig()
397 {
398     if( !tree->selectionModel()->selectedRows().isEmpty() )
399     {
400         saveConfig( tree->selectionModel()->selectedRows().first() );
401     }
402 }
403 
404 
pageChanged()405 void LaunchConfigurationDialog::pageChanged()
406 {
407     currentPageChanged = true;
408     buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true );
409 }
410 
modelChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)411 void LaunchConfigurationDialog::modelChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
412 {
413     if (tree->selectionModel())
414     {
415         QModelIndex index = tree->selectionModel()->selectedRows().first();
416         if (index.row() >= topLeft.row() && index.row() <= bottomRight.row() && bottomRight.column() == 1)
417             selectionChanged(tree->selectionModel()->selection(), tree->selectionModel()->selection());
418     }
419 }
420 
deleteConfiguration()421 void LaunchConfigurationDialog::deleteConfiguration()
422 {
423     if( !tree->selectionModel()->selectedRows().isEmpty() )
424     {
425         model->deleteConfiguration( tree->selectionModel()->selectedRows().first() );
426         tree->resizeColumnToContents( 0 );
427     }
428 }
429 
updateNameLabel(LaunchConfiguration * l)430 void LaunchConfigurationDialog::updateNameLabel( LaunchConfiguration* l )
431 {
432     if( l )
433     {
434         configName->setText( i18n("Editing %2: <b>%1</b>", l->name(), l->type()->name() ) );
435     } else
436     {
437         configName->clear();
438     }
439 }
440 
createConfiguration()441 void LaunchConfigurationDialog::createConfiguration()
442 {
443     if( !tree->selectionModel()->selectedRows().isEmpty() )
444     {
445         QModelIndex idx = tree->selectionModel()->selectedRows().first();
446         if( idx.parent().isValid() )
447         {
448             idx = idx.parent();
449         }
450         model->createConfiguration( idx );
451         QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx );
452         tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
453         tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
454         tree->edit( newindex );
455         tree->resizeColumnToContents( 0 );
456     }
457 }
458 
addConfiguration(ILaunchConfiguration * _launch)459 void LaunchConfigurationDialog::addConfiguration(ILaunchConfiguration* _launch)
460 {
461     auto* launch = dynamic_cast<LaunchConfiguration*>(_launch);
462     Q_ASSERT(launch);
463     int row = launch->project() ? model->findItemForProject(launch->project())->row : 0;
464     QModelIndex idx  = model->index(row, 0);
465 
466     model->addConfiguration(launch, idx);
467 
468     QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx );
469     tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
470     tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
471     tree->edit( newindex );
472     tree->resizeColumnToContents( 0 );
473 }
474 
LaunchConfigurationsModel(QObject * parent)475 LaunchConfigurationsModel::LaunchConfigurationsModel(QObject* parent): QAbstractItemModel(parent)
476 {
477     auto* global = new GenericPageItem;
478     global->text = i18n("Global");
479     global->row = 0;
480     const auto projects = Core::self()->projectController()->projects();
481     topItems.reserve(1 + projects.size());
482     topItems << global;
483     for (IProject* p :  projects) {
484         auto* t = new ProjectItem;
485         t->project = p;
486         t->row = topItems.count();
487         topItems << t;
488     }
489     const auto launchConfigurations = Core::self()->runControllerInternal()->launchConfigurationsInternal();
490     for (LaunchConfiguration* l : launchConfigurations) {
491         addItemForLaunchConfig( l );
492     }
493 }
494 
addItemForLaunchConfig(LaunchConfiguration * l)495 void LaunchConfigurationsModel::addItemForLaunchConfig( LaunchConfiguration* l )
496 {
497     auto* t = new LaunchItem;
498     t->launch = l;
499     TreeItem* parent;
500     if( l->project() ) {
501         parent = findItemForProject( l->project() );
502     } else {
503         parent = topItems.at(0);
504     }
505     t->parent = parent;
506     t->row = parent->children.count();
507     parent->children.append( t );
508     addLaunchModeItemsForLaunchConfig ( t );
509 }
510 
addLaunchModeItemsForLaunchConfig(LaunchItem * t)511 void LaunchConfigurationsModel::addLaunchModeItemsForLaunchConfig ( LaunchItem* t )
512 {
513     QList<TreeItem*> items;
514     QSet<QString> modes;
515     const auto launchers = t->launch->type()->launchers();
516     for (ILauncher* launcher : launchers) {
517         const auto supportedModes = launcher->supportedModes();
518         for (const QString& mode : supportedModes) {
519             if( !modes.contains( mode ) && launcher->configPages().count() > 0 )
520             {
521                 modes.insert( mode );
522                 auto* lmi = new LaunchModeItem;
523                 lmi->mode = Core::self()->runController()->launchModeForId( mode );
524                 lmi->parent = t;
525                 lmi->row = t->children.count();
526                 items.append( lmi );
527             }
528         }
529     }
530     if( !items.isEmpty() )
531     {
532         QModelIndex p = indexForConfig( t->launch );
533         beginInsertRows( p, t->children.count(), t->children.count() + items.count() - 1  );
534         t->children.append( items );
535         endInsertRows();
536     }
537 }
538 
findItemForProject(IProject * p) const539 LaunchConfigurationsModel::ProjectItem* LaunchConfigurationsModel::findItemForProject(IProject* p) const
540 {
541     for (TreeItem* t : topItems) {
542         auto* pi = dynamic_cast<ProjectItem*>( t );
543         if( pi && pi->project == p )
544         {
545             return pi;
546         }
547     }
548     Q_ASSERT(false);
549     return nullptr;
550 }
551 
columnCount(const QModelIndex & parent) const552 int LaunchConfigurationsModel::columnCount(const QModelIndex& parent) const
553 {
554     Q_UNUSED( parent );
555     return 2;
556 }
557 
data(const QModelIndex & index,int role) const558 QVariant LaunchConfigurationsModel::data(const QModelIndex& index, int role) const
559 {
560     if( index.isValid() && index.column() >= 0 && index.column() < 2 )
561     {
562         auto* t = static_cast<TreeItem*>( index.internalPointer() );
563         switch( role )
564         {
565             case Qt::DisplayRole:
566             {
567                 auto* li = dynamic_cast<LaunchItem*>( t );
568                 if( li )
569                 {
570                     if( index.column() == 0 )
571                     {
572                         return li->launch->name();
573                     } else if( index.column() == 1 )
574                     {
575                         return li->launch->type()->name();
576                     }
577                 }
578                 auto* pi = dynamic_cast<ProjectItem*>( t );
579                 if( pi && index.column() == 0 )
580                 {
581                     return pi->project->name();
582                 }
583                 auto* gpi = dynamic_cast<GenericPageItem*>( t );
584                 if( gpi && index.column() == 0 )
585                 {
586                     return gpi->text;
587                 }
588                 auto* lmi = dynamic_cast<LaunchModeItem*>( t );
589                 if( lmi )
590                 {
591                     if( index.column() == 0 )
592                     {
593                         return lmi->mode->name();
594                     } else if( index.column() == 1 )
595                     {
596                         LaunchConfiguration* l = configForIndex( index );
597                         return l->type()->launcherForId( l->launcherForMode( lmi->mode->id() ) )->name();
598                     }
599                 }
600                 break;
601             }
602             case Qt::DecorationRole:
603             {
604                 auto* li = dynamic_cast<LaunchItem*>( t );
605                 if( index.column() == 0 && li )
606                 {
607                     return li->launch->type()->icon();
608                 }
609                 auto* lmi = dynamic_cast<LaunchModeItem*>( t );
610                 if( lmi && index.column() == 0 )
611                 {
612                     return lmi->mode->icon();
613                 }
614                 if ( index.column() == 0 && !index.parent().isValid() ) {
615                     if (index.row() == 0) {
616                         // global item
617                         return QIcon::fromTheme(QStringLiteral("folder"));
618                     } else {
619                         // project item
620                         return QIcon::fromTheme(QStringLiteral("folder-development"));
621                     }
622                 }
623                 break;
624             }
625             case Qt::EditRole:
626             {
627                 auto* li = dynamic_cast<LaunchItem*>( t );
628                 if( li )
629                 {
630                     if( index.column() == 0 )
631                     {
632                         return li->launch->name();
633                     } else if ( index.column() == 1 )
634                     {
635                         return li->launch->type()->id();
636                     }
637                 }
638                 auto* lmi = dynamic_cast<LaunchModeItem*>( t );
639                 if( lmi && index.column() == 1  )
640                 {
641                     return configForIndex( index )->launcherForMode( lmi->mode->id() );
642                 }
643                 break;
644             }
645             default:
646                 break;
647         }
648     }
649     return QVariant();
650 }
651 
index(int row,int column,const QModelIndex & parent) const652 QModelIndex LaunchConfigurationsModel::index(int row, int column, const QModelIndex& parent) const
653 {
654     if( !hasIndex( row, column, parent ) )
655         return QModelIndex();
656     TreeItem* tree;
657 
658     if( !parent.isValid() )
659     {
660         tree = topItems.at( row );
661     } else
662     {
663         auto* t = static_cast<TreeItem*>( parent.internalPointer() );
664         tree = t->children.at( row );
665     }
666     if( tree )
667     {
668         return createIndex( row, column, tree );
669     }
670     return QModelIndex();
671 }
672 
parent(const QModelIndex & child) const673 QModelIndex LaunchConfigurationsModel::parent(const QModelIndex& child) const
674 {
675     if( child.isValid()  )
676     {
677         auto* t = static_cast<TreeItem*>( child.internalPointer() );
678         if( t->parent )
679         {
680             return createIndex( t->parent->row, 0, t->parent );
681         }
682     }
683     return QModelIndex();
684 }
685 
rowCount(const QModelIndex & parent) const686 int LaunchConfigurationsModel::rowCount(const QModelIndex& parent) const
687 {
688     if( parent.column() > 0 )
689         return 0;
690     if( parent.isValid() )
691     {
692         auto* t = static_cast<TreeItem*>( parent.internalPointer() );
693         return t->children.count();
694     } else
695     {
696         return topItems.count();
697     }
698     return 0;
699 }
700 
headerData(int section,Qt::Orientation orientation,int role) const701 QVariant LaunchConfigurationsModel::headerData(int section, Qt::Orientation orientation, int role) const
702 {
703     if( orientation == Qt::Horizontal && role == Qt::DisplayRole )
704     {
705         if( section == 0 )
706         {
707             return i18nc("@title:column Name of the Launch Configurations", "Name");
708         } else if( section == 1 )
709         {
710             return i18nc("@title:column Type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type");
711         }
712     }
713     return QVariant();
714 }
715 
flags(const QModelIndex & index) const716 Qt::ItemFlags LaunchConfigurationsModel::flags(const QModelIndex& index) const
717 {
718     if( index.isValid() && index.column() >= 0
719         && index.column() < columnCount( QModelIndex() ) )
720     {
721         auto* t = static_cast<TreeItem*>( index.internalPointer() );
722         if( t && ( dynamic_cast<LaunchItem*>( t ) || ( dynamic_cast<LaunchModeItem*>( t ) && index.column() == 1 ) ) )
723         {
724             return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable );
725         } else if( t )
726         {
727             return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
728         }
729     }
730     return Qt::NoItemFlags;
731 }
732 
setData(const QModelIndex & index,const QVariant & value,int role)733 bool LaunchConfigurationsModel::setData(const QModelIndex& index, const QVariant& value, int role)
734 {
735     if( index.isValid() && index.parent().isValid() && role == Qt::EditRole )
736     {
737         if( index.row() >= 0 && index.row() < rowCount( index.parent() ) )
738         {
739             auto* t = dynamic_cast<LaunchItem*>( static_cast<TreeItem*>( index.internalPointer() ) );
740             if( t )
741             {
742                 if( index.column() == 0 )
743                 {
744                     t->launch->setName( value.toString() );
745                 } else if( index.column() == 1 )
746                 {
747                     if (t->launch->type()->id() != value.toString()) {
748                         t->launch->setType( value.toString() );
749                         QModelIndex p = indexForConfig(t->launch);
750                         qCDebug(SHELL) << data(p);
751                         beginRemoveRows( p, 0, t->children.count() );
752                         qDeleteAll( t->children );
753                         t->children.clear();
754                         endRemoveRows();
755                         addLaunchModeItemsForLaunchConfig( t );
756                     }
757                 }
758                 emit dataChanged(index, index);
759                 return true;
760             }
761             auto* lmi = dynamic_cast<LaunchModeItem*>( static_cast<TreeItem*>( index.internalPointer() ) );
762             if( lmi )
763             {
764                 if( index.column() == 1 && index.data(Qt::EditRole)!=value)
765                 {
766                     LaunchConfiguration* l = configForIndex( index );
767                     l->setLauncherForMode( lmi->mode->id(), value.toString() );
768                     emit dataChanged(index, index);
769                     return true;
770                 }
771             }
772         }
773     }
774     return false;
775 }
776 
modeForIndex(const QModelIndex & idx) const777 ILaunchMode* LaunchConfigurationsModel::modeForIndex( const QModelIndex& idx ) const
778 {
779     if( idx.isValid() )
780     {
781         auto* item = dynamic_cast<LaunchModeItem*>( static_cast<TreeItem*>( idx.internalPointer() ) );
782         if( item )
783         {
784             return item->mode;
785         }
786     }
787     return nullptr;
788 }
789 
configForIndex(const QModelIndex & idx) const790 LaunchConfiguration* LaunchConfigurationsModel::configForIndex(const QModelIndex& idx ) const
791 {
792     if( idx.isValid() )
793     {
794         auto* item = dynamic_cast<LaunchItem*>( static_cast<TreeItem*>( idx.internalPointer() ) );
795         if( item )
796         {
797             return item->launch;
798         }
799         auto* lmitem = dynamic_cast<LaunchModeItem*>( static_cast<TreeItem*>( idx.internalPointer() ) );
800         if( lmitem )
801         {
802             return dynamic_cast<LaunchItem*>( lmitem->parent )->launch;
803         }
804     }
805     return nullptr;
806 }
807 
indexForConfig(LaunchConfiguration * l) const808 QModelIndex LaunchConfigurationsModel::indexForConfig( LaunchConfiguration* l ) const
809 {
810     if( l )
811     {
812         TreeItem* tparent = topItems.at( 0 );
813         if( l->project() )
814         {
815             for (TreeItem* t : topItems) {
816                 auto* pi = dynamic_cast<ProjectItem*>( t );
817                 if( pi && pi->project == l->project() )
818                 {
819                     tparent = t;
820                     break;
821                 }
822             }
823         }
824 
825         if( tparent )
826         {
827             for (TreeItem* c : qAsConst(tparent->children)) {
828                 auto* li = dynamic_cast<LaunchItem*>( c );
829                 if( li->launch && li->launch == l )
830                 {
831                     return index( c->row, 0, index( tparent->row, 0, QModelIndex() ) );
832                 }
833             }
834         }
835     }
836     return QModelIndex();
837 }
838 
839 
deleteConfiguration(const QModelIndex & index)840 void LaunchConfigurationsModel::deleteConfiguration( const QModelIndex& index )
841 {
842     auto* t = dynamic_cast<LaunchItem*>( static_cast<TreeItem*>( index.internalPointer() ) );
843     if( !t )
844         return;
845     beginRemoveRows( parent( index ), index.row(), index.row() );
846     t->parent->children.removeAll( t );
847     Core::self()->runControllerInternal()->removeLaunchConfiguration( t->launch );
848     endRemoveRows();
849 }
850 
createConfiguration(const QModelIndex & parent)851 void LaunchConfigurationsModel::createConfiguration(const QModelIndex& parent )
852 {
853     if(!Core::self()->runController()->launchConfigurationTypes().isEmpty())
854     {
855         auto* t = static_cast<TreeItem*>( parent.internalPointer() );
856         auto* ti = dynamic_cast<ProjectItem*>( t );
857 
858         LaunchConfigurationType* type = Core::self()->runController()->launchConfigurationTypes().at(0);
859         QPair<QString,QString> launcher = qMakePair( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() );
860         IProject* p = ( ti ? ti->project : nullptr );
861         ILaunchConfiguration* l = Core::self()->runController()->createLaunchConfiguration( type, launcher, p );
862 
863         addConfiguration(l, parent);
864     }
865 }
866 
addConfiguration(ILaunchConfiguration * l,const QModelIndex & parent)867 void LaunchConfigurationsModel::addConfiguration(ILaunchConfiguration* l, const QModelIndex& parent)
868 {
869     if( parent.isValid() )
870     {
871         beginInsertRows( parent, rowCount( parent ), rowCount( parent ) );
872         addItemForLaunchConfig( dynamic_cast<LaunchConfiguration*>( l ) );
873         endInsertRows();
874     }
875     else
876     {
877         delete l;
878         Q_ASSERT(false && "could not add the configuration");
879     }
880 }
881 
projectForIndex(const QModelIndex & idx)882 IProject* LaunchConfigurationsModel::projectForIndex(const QModelIndex& idx)
883 {
884     if(idx.parent().isValid()) {
885         return projectForIndex(idx.parent());
886     } else {
887         const auto* item = dynamic_cast<const ProjectItem*>(topItems[idx.row()]);
888         return item ? item->project : nullptr;
889     }
890 }
891 
LaunchConfigPagesContainer(const QList<LaunchConfigurationPageFactory * > & factories,QWidget * parent)892 LaunchConfigPagesContainer::LaunchConfigPagesContainer( const QList<LaunchConfigurationPageFactory*>& factories, QWidget* parent )
893     : QWidget(parent)
894 {
895     setLayout( new QVBoxLayout( this ) );
896     layout()->setContentsMargins( 0, 0, 0, 0 );
897     QWidget* parentwidget = this;
898     QTabWidget* tab = nullptr;
899     if( factories.count() > 1 )
900     {
901         tab = new QTabWidget( this );
902         parentwidget = tab;
903         layout()->addWidget( tab );
904     }
905     for (LaunchConfigurationPageFactory* fac : factories) {
906         LaunchConfigurationPage* page = fac->createWidget( parentwidget );
907         if ( page->layout() ) {
908             // remove margins for single page, reset margins for tabbed display
909             const int pageMargin = tab ? -1 : 0;
910             page->layout()->setContentsMargins(pageMargin, pageMargin, pageMargin, pageMargin);
911         }
912         pages.append( page );
913         connect( page, &LaunchConfigurationPage::changed, this, &LaunchConfigPagesContainer::changed );
914         if( tab ) {
915             tab->addTab( page, page->icon(), page->title() );
916         } else
917         {
918             layout()->addWidget( page );
919         }
920     }
921 }
922 
setLaunchConfiguration(KDevelop::LaunchConfiguration * l)923 void LaunchConfigPagesContainer::setLaunchConfiguration( KDevelop::LaunchConfiguration* l )
924 {
925     config = l;
926     for (LaunchConfigurationPage* p : qAsConst(pages)) {
927         p->loadFromConfiguration( config->config(), config->project() );
928     }
929 }
930 
save()931 void LaunchConfigPagesContainer::save()
932 {
933     for (LaunchConfigurationPage* p : qAsConst(pages)) {
934         p->saveToConfiguration( config->config() );
935     }
936     config->config().sync();
937 }
938 
939 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const940 QWidget* LaunchConfigurationModelDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
941 {
942     const auto* model = static_cast<const LaunchConfigurationsModel*>(index.model());
943     ILaunchMode* mode = model->modeForIndex( index );
944     LaunchConfiguration* config = model->configForIndex( index );
945     if( index.column() == 1 && mode && config )
946     {
947         auto* box = new KComboBox( parent );
948         const QList<ILauncher*> launchers = config->type()->launchers();
949         for (auto* launcher : launchers) {
950             if (launcher->supportedModes().contains(mode->id())) {
951                 box->addItem(launcher->name(), launcher->id());
952             }
953         }
954         return box;
955     } else if( !mode && config && index.column() == 1 )
956     {
957         auto* box = new KComboBox( parent );
958         const QList<LaunchConfigurationType*> types = Core::self()->runController()->launchConfigurationTypes();
959         for (auto* type : types) {
960             box->addItem(type->name(), type->id());
961         }
962         return box;
963     }
964     return QStyledItemDelegate::createEditor ( parent, option, index );
965 }
966 
setEditorData(QWidget * editor,const QModelIndex & index) const967 void LaunchConfigurationModelDelegate::setEditorData ( QWidget* editor, const QModelIndex& index ) const
968 {
969     const auto* model = static_cast<const LaunchConfigurationsModel*>(index.model());
970     LaunchConfiguration* config = model->configForIndex( index );
971     if( index.column() == 1 && config )
972     {
973         auto* box = qobject_cast<KComboBox*>( editor );
974         box->setCurrentIndex( box->findData( index.data( Qt::EditRole ) ) );
975     }
976     else
977     {
978         QStyledItemDelegate::setEditorData ( editor, index );
979     }
980 }
981 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const982 void LaunchConfigurationModelDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
983 {
984     auto* lmodel = static_cast<LaunchConfigurationsModel*>(model);
985     LaunchConfiguration* config = lmodel->configForIndex( index );
986     if( index.column() == 1 && config )
987     {
988         auto* box = qobject_cast<KComboBox*>( editor );
989         lmodel->setData( index, box->itemData( box->currentIndex() ) );
990     }
991     else
992     {
993         QStyledItemDelegate::setModelData ( editor, model, index );
994     }
995 }
996 
launchModeChanged(int item)997 void LaunchConfigurationDialog::launchModeChanged(int item)
998 {
999     QModelIndex index = tree->currentIndex();
1000     if(debugger->isVisible() && item>=0)
1001         tree->model()->setData(index.sibling(index.row(), 1), debugger->itemData(item), Qt::EditRole);
1002 }
1003 
1004 }
1005 
1006