1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "environmentwidget.h"
27 
28 #include <coreplugin/fileutils.h>
29 #include <coreplugin/find/itemviewfind.h>
30 
31 #include <utils/algorithm.h>
32 #include <utils/detailswidget.h>
33 #include <utils/environment.h>
34 #include <utils/environmentdialog.h>
35 #include <utils/environmentmodel.h>
36 #include <utils/headerviewstretcher.h>
37 #include <utils/hostosinfo.h>
38 #include <utils/itemviews.h>
39 #include <utils/namevaluevalidator.h>
40 #include <utils/qtcassert.h>
41 #include <utils/stringutils.h>
42 #include <utils/tooltip/tooltip.h>
43 
44 #include <QDialogButtonBox>
45 #include <QDir>
46 #include <QFileDialog>
47 #include <QFileInfo>
48 #include <QHBoxLayout>
49 #include <QKeyEvent>
50 #include <QLineEdit>
51 #include <QPushButton>
52 #include <QString>
53 #include <QStyledItemDelegate>
54 #include <QTreeView>
55 #include <QTreeWidget>
56 #include <QTreeWidgetItem>
57 #include <QVBoxLayout>
58 
59 namespace ProjectExplorer {
60 
61 class PathTreeWidget : public QTreeWidget
62 {
63 public:
sizeHint() const64     QSize sizeHint() const override
65     {
66         return QSize(800, 600);
67     }
68 };
69 
70 class PathListDialog : public QDialog
71 {
72     Q_DECLARE_TR_FUNCTIONS(EnvironmentWidget)
73 public:
PathListDialog(const QString & varName,const QString & paths,QWidget * parent)74     PathListDialog(const QString &varName, const QString &paths, QWidget *parent) : QDialog(parent)
75     {
76         const auto mainLayout = new QVBoxLayout(this);
77         const auto viewLayout = new QHBoxLayout;
78         const auto buttonsLayout = new QVBoxLayout;
79         const auto addButton = new QPushButton(tr("Add..."));
80         const auto removeButton = new QPushButton(tr("Remove"));
81         const auto editButton = new QPushButton(tr("Edit..."));
82         buttonsLayout->addWidget(addButton);
83         buttonsLayout->addWidget(removeButton);
84         buttonsLayout->addWidget(editButton);
85         buttonsLayout->addStretch(1);
86         const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
87                                                     | QDialogButtonBox::Cancel);
88         viewLayout->addWidget(&m_view);
89         viewLayout->addLayout(buttonsLayout);
90         mainLayout->addLayout(viewLayout);
91         mainLayout->addWidget(buttonBox);
92 
93         m_view.setHeaderLabel(varName);
94         m_view.setDragDropMode(QAbstractItemView::InternalMove);
95         const QStringList pathList = paths.split(Utils::HostOsInfo::pathListSeparator(),
96                                                  Qt::SkipEmptyParts);
97         for (const QString &path : pathList)
98             addPath(path);
99 
100         connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
101         connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
102         connect(addButton, &QPushButton::clicked, this, [this] {
103             const QString dir = QDir::toNativeSeparators(
104                         QFileDialog::getExistingDirectory(this, tr("Choose Directory")));
105             if (!dir.isEmpty())
106                 addPath(dir);
107         });
108         connect(removeButton, &QPushButton::clicked, this, [this] {
109             const QList<QTreeWidgetItem *> selected = m_view.selectedItems();
110             QTC_ASSERT(selected.count() == 1, return);
111             delete selected.first();
112         });
113         connect(editButton, &QPushButton::clicked, this, [this] {
114             const QList<QTreeWidgetItem *> selected = m_view.selectedItems();
115             QTC_ASSERT(selected.count() == 1, return);
116             m_view.editItem(selected.first(), 0);
117         });
118         const auto updateButtonStates = [this, removeButton, editButton] {
119             const bool hasSelection = !m_view.selectedItems().isEmpty();
120             removeButton->setEnabled(hasSelection);
121             editButton->setEnabled(hasSelection);
122         };
123         connect(m_view.selectionModel(), &QItemSelectionModel::selectionChanged,
124                 this, updateButtonStates);
125         updateButtonStates();
126     }
127 
paths() const128     QString paths() const
129     {
130         QStringList pathList;
131         for (int i = 0; i < m_view.topLevelItemCount(); ++i)
132             pathList << m_view.topLevelItem(i)->text(0);
133         return pathList.join(Utils::HostOsInfo::pathListSeparator());
134     }
135 
136 private:
addPath(const QString & path)137     void addPath(const QString &path)
138     {
139         const auto item = new QTreeWidgetItem(&m_view, {path});
140         item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable
141                        | Qt::ItemIsDragEnabled);
142     }
143 
144     PathTreeWidget m_view;
145 };
146 
147 class EnvironmentDelegate : public QStyledItemDelegate
148 {
149 public:
EnvironmentDelegate(Utils::EnvironmentModel * model,QTreeView * view)150     EnvironmentDelegate(Utils::EnvironmentModel *model,
151                         QTreeView *view)
152         : QStyledItemDelegate(view), m_model(model), m_view(view)
153     {}
154 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const155     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
156     {
157         QWidget *w = QStyledItemDelegate::createEditor(parent, option, index);
158         if (index.column() != 0)
159             return w;
160 
161         if (auto edit = qobject_cast<QLineEdit *>(w))
162             edit->setValidator(new Utils::NameValueValidator(
163                 edit, m_model, m_view, index, EnvironmentWidget::tr("Variable already exists.")));
164         return w;
165     }
166 private:
167     Utils::EnvironmentModel *m_model;
168     QTreeView *m_view;
169 };
170 
171 
172 ////
173 // EnvironmentWidget::EnvironmentWidget
174 ////
175 
176 class EnvironmentWidgetPrivate
177 {
178 public:
179     Utils::EnvironmentModel *m_model;
180     EnvironmentWidget::Type m_type = EnvironmentWidget::TypeLocal;
181     QString m_baseEnvironmentText;
182     EnvironmentWidget::OpenTerminalFunc m_openTerminalFunc;
183     Utils::DetailsWidget *m_detailsContainer;
184     QTreeView *m_environmentView;
185     QPushButton *m_editButton;
186     QPushButton *m_addButton;
187     QPushButton *m_resetButton;
188     QPushButton *m_unsetButton;
189     QPushButton *m_toggleButton;
190     QPushButton *m_batchEditButton;
191     QPushButton *m_appendPathButton = nullptr;
192     QPushButton *m_prependPathButton = nullptr;
193     QPushButton *m_terminalButton;
194 };
195 
EnvironmentWidget(QWidget * parent,Type type,QWidget * additionalDetailsWidget)196 EnvironmentWidget::EnvironmentWidget(QWidget *parent, Type type, QWidget *additionalDetailsWidget)
197     : QWidget(parent), d(std::make_unique<EnvironmentWidgetPrivate>())
198 {
199     d->m_model = new Utils::EnvironmentModel();
200     d->m_type = type;
201     connect(d->m_model, &Utils::EnvironmentModel::userChangesChanged,
202             this, &EnvironmentWidget::userChangesChanged);
203     connect(d->m_model, &QAbstractItemModel::modelReset,
204             this, &EnvironmentWidget::invalidateCurrentIndex);
205 
206     connect(d->m_model, &Utils::EnvironmentModel::focusIndex,
207             this, &EnvironmentWidget::focusIndex);
208 
209     auto vbox = new QVBoxLayout(this);
210     vbox->setContentsMargins(0, 0, 0, 0);
211 
212     d->m_detailsContainer = new Utils::DetailsWidget(this);
213 
214     auto details = new QWidget(d->m_detailsContainer);
215     d->m_detailsContainer->setWidget(details);
216     details->setVisible(false);
217 
218     auto vbox2 = new QVBoxLayout(details);
219     vbox2->setContentsMargins(0, 0, 0, 0);
220 
221     if (additionalDetailsWidget)
222         vbox2->addWidget(additionalDetailsWidget);
223 
224     auto horizontalLayout = new QHBoxLayout();
225     horizontalLayout->setContentsMargins(0, 0, 0, 0);
226     auto tree = new Utils::TreeView(this);
227     connect(tree, &QAbstractItemView::activated,
228             tree, [tree](const QModelIndex &idx) { tree->edit(idx); });
229     d->m_environmentView = tree;
230     d->m_environmentView->setModel(d->m_model);
231     d->m_environmentView->setItemDelegate(new EnvironmentDelegate(d->m_model, d->m_environmentView));
232     d->m_environmentView->setMinimumHeight(400);
233     d->m_environmentView->setRootIsDecorated(false);
234     d->m_environmentView->setUniformRowHeights(true);
235     new Utils::HeaderViewStretcher(d->m_environmentView->header(), 1);
236     d->m_environmentView->setSelectionMode(QAbstractItemView::SingleSelection);
237     d->m_environmentView->setSelectionBehavior(QAbstractItemView::SelectItems);
238     d->m_environmentView->setFrameShape(QFrame::NoFrame);
239     QFrame *findWrapper = Core::ItemViewFind::createSearchableWrapper(d->m_environmentView, Core::ItemViewFind::LightColored);
240     findWrapper->setFrameStyle(QFrame::StyledPanel);
241     horizontalLayout->addWidget(findWrapper);
242 
243     auto buttonLayout = new QVBoxLayout();
244 
245     d->m_editButton = new QPushButton(this);
246     d->m_editButton->setText(tr("Ed&it"));
247     buttonLayout->addWidget(d->m_editButton);
248 
249     d->m_addButton = new QPushButton(this);
250     d->m_addButton->setText(tr("&Add"));
251     buttonLayout->addWidget(d->m_addButton);
252 
253     d->m_resetButton = new QPushButton(this);
254     d->m_resetButton->setEnabled(false);
255     d->m_resetButton->setText(tr("&Reset"));
256     buttonLayout->addWidget(d->m_resetButton);
257 
258     d->m_unsetButton = new QPushButton(this);
259     d->m_unsetButton->setEnabled(false);
260     d->m_unsetButton->setText(tr("&Unset"));
261     buttonLayout->addWidget(d->m_unsetButton);
262 
263     d->m_toggleButton = new QPushButton(tr("Disable"), this);
264     buttonLayout->addWidget(d->m_toggleButton);
265     connect(d->m_toggleButton, &QPushButton::clicked, this, [this] {
266         d->m_model->toggleVariable(d->m_environmentView->currentIndex());
267         updateButtons();
268     });
269 
270     if (type == TypeLocal) {
271         d->m_appendPathButton = new QPushButton(this);
272         d->m_appendPathButton->setEnabled(false);
273         d->m_appendPathButton->setText(tr("Append Path..."));
274         buttonLayout->addWidget(d->m_appendPathButton);
275         d->m_prependPathButton = new QPushButton(this);
276         d->m_prependPathButton->setEnabled(false);
277         d->m_prependPathButton->setText(tr("Prepend Path..."));
278         buttonLayout->addWidget(d->m_prependPathButton);
279         connect(d->m_appendPathButton, &QAbstractButton::clicked,
280                 this, &EnvironmentWidget::appendPathButtonClicked);
281         connect(d->m_prependPathButton, &QAbstractButton::clicked,
282                 this, &EnvironmentWidget::prependPathButtonClicked);
283     }
284 
285     d->m_batchEditButton = new QPushButton(this);
286     d->m_batchEditButton->setText(tr("&Batch Edit..."));
287     buttonLayout->addWidget(d->m_batchEditButton);
288 
289     d->m_terminalButton = new QPushButton(this);
290     d->m_terminalButton->setText(tr("Open &Terminal"));
291     d->m_terminalButton->setToolTip(tr("Open a terminal with this environment set up."));
292     d->m_terminalButton->setEnabled(type == TypeLocal);
293     buttonLayout->addWidget(d->m_terminalButton);
294     buttonLayout->addStretch();
295 
296     horizontalLayout->addLayout(buttonLayout);
297     vbox2->addLayout(horizontalLayout);
298 
299     vbox->addWidget(d->m_detailsContainer);
300 
301     connect(d->m_model, &QAbstractItemModel::dataChanged,
302             this, &EnvironmentWidget::updateButtons);
303 
304     connect(d->m_editButton, &QAbstractButton::clicked,
305             this, &EnvironmentWidget::editEnvironmentButtonClicked);
306     connect(d->m_addButton, &QAbstractButton::clicked,
307             this, &EnvironmentWidget::addEnvironmentButtonClicked);
308     connect(d->m_resetButton, &QAbstractButton::clicked,
309             this, &EnvironmentWidget::removeEnvironmentButtonClicked);
310     connect(d->m_unsetButton, &QAbstractButton::clicked,
311             this, &EnvironmentWidget::unsetEnvironmentButtonClicked);
312     connect(d->m_batchEditButton, &QAbstractButton::clicked,
313             this, &EnvironmentWidget::batchEditEnvironmentButtonClicked);
314     connect(d->m_environmentView->selectionModel(), &QItemSelectionModel::currentChanged,
315             this, &EnvironmentWidget::environmentCurrentIndexChanged);
316     connect(d->m_terminalButton, &QAbstractButton::clicked,
317             this, [this] {
318         Utils::Environment env = d->m_model->baseEnvironment();
319         env.modify(d->m_model->userChanges());
320         if (d->m_openTerminalFunc)
321             d->m_openTerminalFunc(env);
322         else
323             Core::FileUtils::openTerminal(QDir::currentPath(), env);
324     });
325     connect(d->m_detailsContainer, &Utils::DetailsWidget::linkActivated,
326             this, &EnvironmentWidget::linkActivated);
327 
328     connect(d->m_model, &Utils::EnvironmentModel::userChangesChanged,
329             this, &EnvironmentWidget::updateSummaryText);
330 }
331 
~EnvironmentWidget()332 EnvironmentWidget::~EnvironmentWidget()
333 {
334     delete d->m_model;
335     d->m_model = nullptr;
336 }
337 
focusIndex(const QModelIndex & index)338 void EnvironmentWidget::focusIndex(const QModelIndex &index)
339 {
340     d->m_environmentView->setCurrentIndex(index);
341     d->m_environmentView->setFocus();
342     // When the current item changes as a result of the call above,
343     // QAbstractItemView::currentChanged() is called. That calls scrollTo(current),
344     // using the default EnsureVisible scroll hint, whereas we want PositionAtTop,
345     // because it ensures that the user doesn't have to scroll down when they've
346     // added a new environment variable and want to edit its value; they'll be able
347     // to see its value as they're typing it.
348     // This only helps to a certain degree - variables whose names start with letters
349     // later in the alphabet cause them fall within the "end" of the view's range,
350     // making it impossible to position them at the top of the view.
351     d->m_environmentView->scrollTo(index, QAbstractItemView::PositionAtTop);
352 }
353 
setBaseEnvironment(const Utils::Environment & env)354 void EnvironmentWidget::setBaseEnvironment(const Utils::Environment &env)
355 {
356     d->m_model->setBaseEnvironment(env);
357 }
358 
setBaseEnvironmentText(const QString & text)359 void EnvironmentWidget::setBaseEnvironmentText(const QString &text)
360 {
361     d->m_baseEnvironmentText = text;
362     updateSummaryText();
363 }
364 
userChanges() const365 Utils::EnvironmentItems EnvironmentWidget::userChanges() const
366 {
367     return d->m_model->userChanges();
368 }
369 
setUserChanges(const Utils::EnvironmentItems & list)370 void EnvironmentWidget::setUserChanges(const Utils::EnvironmentItems &list)
371 {
372     d->m_model->setUserChanges(list);
373     updateSummaryText();
374 }
375 
setOpenTerminalFunc(const EnvironmentWidget::OpenTerminalFunc & func)376 void EnvironmentWidget::setOpenTerminalFunc(const EnvironmentWidget::OpenTerminalFunc &func)
377 {
378     d->m_openTerminalFunc = func;
379     d->m_terminalButton->setVisible(bool(func));
380 }
381 
expand()382 void EnvironmentWidget::expand()
383 {
384     d->m_detailsContainer->setState(Utils::DetailsWidget::Expanded);
385 }
386 
updateSummaryText()387 void EnvironmentWidget::updateSummaryText()
388 {
389     Utils::EnvironmentItems list = d->m_model->userChanges();
390     Utils::EnvironmentItem::sort(&list);
391 
392     QString text;
393     foreach (const Utils::EnvironmentItem &item, list) {
394         if (item.name != Utils::EnvironmentModel::tr("<VARIABLE>")) {
395             if (!d->m_baseEnvironmentText.isEmpty() || !text.isEmpty())
396                 text.append(QLatin1String("<br>"));
397             switch (item.operation) {
398             case Utils::EnvironmentItem::Unset:
399                 text.append(tr("Unset <a href=\"%1\"><b>%1</b></a>").arg(item.name.toHtmlEscaped()));
400                 break;
401             case Utils::EnvironmentItem::SetEnabled:
402                 text.append(tr("Set <a href=\"%1\"><b>%1</b></a> to <b>%2</b>").arg(item.name.toHtmlEscaped(), item.value.toHtmlEscaped()));
403                 break;
404             case Utils::EnvironmentItem::Append:
405                 text.append(tr("Append <b>%2</b> to <a href=\"%1\"><b>%1</b></a>").arg(item.name.toHtmlEscaped(), item.value.toHtmlEscaped()));
406                 break;
407             case Utils::EnvironmentItem::Prepend:
408                 text.append(tr("Prepend <b>%2</b> to <a href=\"%1\"><b>%1</b></a>").arg(item.name.toHtmlEscaped(), item.value.toHtmlEscaped()));
409                 break;
410             case Utils::EnvironmentItem::SetDisabled:
411                 text.append(tr("Set <a href=\"%1\"><b>%1</b></a> to <b>%2</b> [disabled]").arg(item.name.toHtmlEscaped(), item.value.toHtmlEscaped()));
412                 break;
413             }
414         }
415     }
416 
417     if (text.isEmpty()) {
418         //: %1 is "System Environment" or some such.
419         if (!d->m_baseEnvironmentText.isEmpty())
420             text.prepend(tr("Use <b>%1</b>").arg(d->m_baseEnvironmentText));
421         else
422             text.prepend(tr("<b>No environment changes</b>"));
423     } else {
424         //: Yup, word puzzle. The Set/Unset phrases above are appended to this.
425         //: %1 is "System Environment" or some such.
426         if (!d->m_baseEnvironmentText.isEmpty())
427             text.prepend(tr("Use <b>%1</b> and").arg(d->m_baseEnvironmentText));
428     }
429 
430     d->m_detailsContainer->setSummaryText(text);
431 }
432 
linkActivated(const QString & link)433 void EnvironmentWidget::linkActivated(const QString &link)
434 {
435     d->m_detailsContainer->setState(Utils::DetailsWidget::Expanded);
436     QModelIndex idx = d->m_model->variableToIndex(link);
437     focusIndex(idx);
438 }
439 
updateButtons()440 void EnvironmentWidget::updateButtons()
441 {
442     environmentCurrentIndexChanged(d->m_environmentView->currentIndex());
443 }
444 
editEnvironmentButtonClicked()445 void EnvironmentWidget::editEnvironmentButtonClicked()
446 {
447     const QModelIndex current = d->m_environmentView->currentIndex();
448     if (current.column() == 1
449             && d->m_type == TypeLocal
450             && d->m_model->currentEntryIsPathList(current)) {
451         PathListDialog dlg(d->m_model->indexToVariable(current),
452                            d->m_model->data(current).toString(), this);
453         if (dlg.exec() == QDialog::Accepted)
454             d->m_model->setData(current, dlg.paths());
455     } else {
456         d->m_environmentView->edit(current);
457     }
458 }
459 
addEnvironmentButtonClicked()460 void EnvironmentWidget::addEnvironmentButtonClicked()
461 {
462     QModelIndex index = d->m_model->addVariable();
463     d->m_environmentView->setCurrentIndex(index);
464     d->m_environmentView->edit(index);
465 }
466 
removeEnvironmentButtonClicked()467 void EnvironmentWidget::removeEnvironmentButtonClicked()
468 {
469     const QString &name = d->m_model->indexToVariable(d->m_environmentView->currentIndex());
470     d->m_model->resetVariable(name);
471 }
472 
473 // unset in Merged Environment Mode means, unset if it comes from the base environment
474 // or remove when it is just a change we added
unsetEnvironmentButtonClicked()475 void EnvironmentWidget::unsetEnvironmentButtonClicked()
476 {
477     const QString &name = d->m_model->indexToVariable(d->m_environmentView->currentIndex());
478     if (!d->m_model->canReset(name))
479         d->m_model->resetVariable(name);
480     else
481         d->m_model->unsetVariable(name);
482 }
483 
amendPathList(Utils::NameValueItem::Operation op)484 void EnvironmentWidget::amendPathList(Utils::NameValueItem::Operation op)
485 {
486     const QString varName = d->m_model->indexToVariable(d->m_environmentView->currentIndex());
487     const QString dir = QDir::toNativeSeparators(
488                 QFileDialog::getExistingDirectory(this, tr("Choose Directory")));
489     if (dir.isEmpty())
490         return;
491     Utils::NameValueItems changes = d->m_model->userChanges();
492     changes.append({varName, dir, op});
493     d->m_model->setUserChanges(changes);
494 }
495 
appendPathButtonClicked()496 void EnvironmentWidget::appendPathButtonClicked()
497 {
498     amendPathList(Utils::NameValueItem::Append);
499 }
500 
prependPathButtonClicked()501 void EnvironmentWidget::prependPathButtonClicked()
502 {
503     amendPathList(Utils::NameValueItem::Prepend);
504 }
505 
batchEditEnvironmentButtonClicked()506 void EnvironmentWidget::batchEditEnvironmentButtonClicked()
507 {
508     const Utils::EnvironmentItems changes = d->m_model->userChanges();
509 
510     const auto newChanges = Utils::EnvironmentDialog::getEnvironmentItems(this, changes);
511 
512     if (newChanges)
513         d->m_model->setUserChanges(*newChanges);
514 }
515 
environmentCurrentIndexChanged(const QModelIndex & current)516 void EnvironmentWidget::environmentCurrentIndexChanged(const QModelIndex &current)
517 {
518     if (current.isValid()) {
519         d->m_editButton->setEnabled(true);
520         const QString &name = d->m_model->indexToVariable(current);
521         bool modified = d->m_model->canReset(name) && d->m_model->changes(name);
522         bool unset = d->m_model->isUnset(name);
523         d->m_resetButton->setEnabled(modified || unset);
524         d->m_unsetButton->setEnabled(!unset);
525         d->m_toggleButton->setEnabled(!unset);
526         d->m_toggleButton->setText(d->m_model->isEnabled(name) ? tr("Disable") : tr("Enable"));
527     } else {
528         d->m_editButton->setEnabled(false);
529         d->m_resetButton->setEnabled(false);
530         d->m_unsetButton->setEnabled(false);
531         d->m_toggleButton->setEnabled(false);
532         d->m_toggleButton->setText(tr("Disable"));
533     }
534     if (d->m_appendPathButton) {
535         const bool isPathList = d->m_model->currentEntryIsPathList(current);
536         d->m_appendPathButton->setEnabled(isPathList);
537         d->m_prependPathButton->setEnabled(isPathList);
538     }
539 }
540 
invalidateCurrentIndex()541 void EnvironmentWidget::invalidateCurrentIndex()
542 {
543     environmentCurrentIndexChanged(QModelIndex());
544 }
545 
546 } // namespace ProjectExplorer
547