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