forked from qt-creator/qt-creator
readability-make-member-function-const finds lots of member functions that could be made const. This change just picks getter functions that really should be const. readability-convert-member-functions-to-static finds non-static member functions which do not access this. This change turns most of them into static ones, but leaves some non static to keep the class API consistent. readability-static-accessed-through-instance fixes the places where the originally non-static, now static functions were called through instance. Change-Id: I8cf16c01f7988a7c9d073b5f8ede6a9706b94fb0 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
1037 lines
30 KiB
C++
1037 lines
30 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "wizard.h"
|
|
|
|
#include "algorithm.h"
|
|
#include "hostosinfo.h"
|
|
#include "qtcassert.h"
|
|
#include "wizardpage.h"
|
|
|
|
#include <utils/theme/theme.h>
|
|
|
|
#include <QDialog>
|
|
#include <QDialogButtonBox>
|
|
#include <QHash>
|
|
#include <QIcon>
|
|
#include <QKeyEvent>
|
|
#include <QLabel>
|
|
#include <QMap>
|
|
#include <QScrollArea>
|
|
#include <QVBoxLayout>
|
|
#include <QVariant>
|
|
|
|
|
|
/*! \class Utils::Wizard
|
|
|
|
\brief The Wizard class implements a wizard with a progress bar on the left.
|
|
|
|
Informs the user about the progress.
|
|
*/
|
|
|
|
namespace Utils {
|
|
|
|
class ProgressItemWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
ProgressItemWidget(const QPixmap &indicatorPixmap, const QString &title, QWidget *parent = nullptr)
|
|
: QWidget(parent),
|
|
m_indicatorVisible(false),
|
|
m_indicatorPixmap(indicatorPixmap)
|
|
{
|
|
m_indicatorLabel = new QLabel(this);
|
|
m_indicatorLabel->setFixedSize(m_indicatorPixmap.size());
|
|
m_titleLabel = new QLabel(title, this);
|
|
auto l = new QHBoxLayout(this);
|
|
l->setContentsMargins(0, 0, 0, 0);
|
|
l->addWidget(m_indicatorLabel);
|
|
l->addWidget(m_titleLabel);
|
|
}
|
|
void setIndicatorVisible(bool visible) {
|
|
if (m_indicatorVisible == visible)
|
|
return;
|
|
m_indicatorVisible = visible;
|
|
if (m_indicatorVisible)
|
|
m_indicatorLabel->setPixmap(m_indicatorPixmap);
|
|
else
|
|
m_indicatorLabel->setPixmap(QPixmap());
|
|
}
|
|
void setTitle(const QString &title) {
|
|
m_titleLabel->setText(title);
|
|
}
|
|
void setWordWrap(bool wrap) {
|
|
m_titleLabel->setWordWrap(wrap);
|
|
}
|
|
|
|
private:
|
|
bool m_indicatorVisible;
|
|
QPixmap m_indicatorPixmap;
|
|
QLabel *m_indicatorLabel;
|
|
QLabel *m_titleLabel;
|
|
};
|
|
|
|
class LinearProgressWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
LinearProgressWidget(WizardProgress *progress, QWidget *parent = nullptr);
|
|
|
|
private:
|
|
void slotItemAdded(WizardProgressItem *item);
|
|
void slotItemRemoved(WizardProgressItem *item);
|
|
void slotItemChanged(WizardProgressItem *item);
|
|
void slotNextItemsChanged(WizardProgressItem *item, const QList<WizardProgressItem *> &nextItems);
|
|
void slotNextShownItemChanged(WizardProgressItem *item, WizardProgressItem *nextItem);
|
|
void slotStartItemChanged(WizardProgressItem *item);
|
|
void slotCurrentItemChanged(WizardProgressItem *item);
|
|
void recreateLayout();
|
|
void updateProgress();
|
|
void disableUpdates();
|
|
void enableUpdates();
|
|
|
|
QVBoxLayout *m_mainLayout;
|
|
QVBoxLayout *m_itemWidgetLayout;
|
|
WizardProgress *m_wizardProgress;
|
|
QMap<WizardProgressItem *, ProgressItemWidget *> m_itemToItemWidget;
|
|
QMap<ProgressItemWidget *, WizardProgressItem *> m_itemWidgetToItem;
|
|
QList<WizardProgressItem *> m_visibleItems;
|
|
ProgressItemWidget *m_dotsItemWidget;
|
|
int m_disableUpdatesCount;
|
|
QPixmap m_indicatorPixmap;
|
|
};
|
|
|
|
LinearProgressWidget::LinearProgressWidget(WizardProgress *progress, QWidget *parent)
|
|
:
|
|
QWidget(parent),
|
|
m_dotsItemWidget(nullptr),
|
|
m_disableUpdatesCount(0)
|
|
{
|
|
m_indicatorPixmap = QIcon::fromTheme(QLatin1String("go-next"), QIcon(QLatin1String(":/utils/images/arrow.png"))).pixmap(16);
|
|
m_wizardProgress = progress;
|
|
m_mainLayout = new QVBoxLayout(this);
|
|
m_itemWidgetLayout = new QVBoxLayout();
|
|
auto spacer = new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding);
|
|
m_mainLayout->addLayout(m_itemWidgetLayout);
|
|
m_mainLayout->addSpacerItem(spacer);
|
|
|
|
m_dotsItemWidget = new ProgressItemWidget(m_indicatorPixmap, tr("..."), this);
|
|
m_dotsItemWidget->setVisible(false);
|
|
m_dotsItemWidget->setEnabled(false);
|
|
|
|
connect(m_wizardProgress, &WizardProgress::itemAdded,
|
|
this, &LinearProgressWidget::slotItemAdded);
|
|
connect(m_wizardProgress, &WizardProgress::itemRemoved,
|
|
this, &LinearProgressWidget::slotItemRemoved);
|
|
connect(m_wizardProgress, &WizardProgress::itemChanged,
|
|
this, &LinearProgressWidget::slotItemChanged);
|
|
connect(m_wizardProgress, &WizardProgress::nextItemsChanged,
|
|
this, &LinearProgressWidget::slotNextItemsChanged);
|
|
connect(m_wizardProgress, &WizardProgress::nextShownItemChanged,
|
|
this, &LinearProgressWidget::slotNextShownItemChanged);
|
|
connect(m_wizardProgress, &WizardProgress::startItemChanged,
|
|
this, &LinearProgressWidget::slotStartItemChanged);
|
|
connect(m_wizardProgress, &WizardProgress::currentItemChanged,
|
|
this, &LinearProgressWidget::slotCurrentItemChanged);
|
|
|
|
QList<WizardProgressItem *> items = m_wizardProgress->items();
|
|
for (int i = 0; i < items.count(); i++)
|
|
slotItemAdded(items.at(i));
|
|
recreateLayout();
|
|
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
}
|
|
|
|
void LinearProgressWidget::slotItemAdded(WizardProgressItem *item)
|
|
{
|
|
ProgressItemWidget *itemWidget = new ProgressItemWidget(m_indicatorPixmap, item->title(), this);
|
|
itemWidget->setVisible(false);
|
|
itemWidget->setWordWrap(item->titleWordWrap());
|
|
m_itemToItemWidget.insert(item, itemWidget);
|
|
m_itemWidgetToItem.insert(itemWidget, item);
|
|
}
|
|
|
|
void LinearProgressWidget::slotItemRemoved(WizardProgressItem *item)
|
|
{
|
|
ProgressItemWidget *itemWidget = m_itemToItemWidget.value(item);
|
|
if (!itemWidget)
|
|
return;
|
|
|
|
m_itemWidgetToItem.remove(itemWidget);
|
|
m_itemToItemWidget.remove(item);
|
|
|
|
recreateLayout();
|
|
|
|
delete itemWidget;
|
|
}
|
|
|
|
void LinearProgressWidget::slotItemChanged(WizardProgressItem *item)
|
|
{
|
|
ProgressItemWidget *itemWidget = m_itemToItemWidget.value(item);
|
|
if (!itemWidget)
|
|
return;
|
|
|
|
itemWidget->setTitle(item->title());
|
|
itemWidget->setWordWrap(item->titleWordWrap());
|
|
}
|
|
|
|
void LinearProgressWidget::slotNextItemsChanged(WizardProgressItem *item, const QList<WizardProgressItem *> &nextItems)
|
|
{
|
|
Q_UNUSED(nextItems)
|
|
if (m_visibleItems.contains(item))
|
|
recreateLayout();
|
|
}
|
|
|
|
void LinearProgressWidget::slotNextShownItemChanged(WizardProgressItem *item, WizardProgressItem *nextItem)
|
|
{
|
|
Q_UNUSED(nextItem)
|
|
if (m_visibleItems.contains(item))
|
|
recreateLayout();
|
|
}
|
|
|
|
void LinearProgressWidget::slotStartItemChanged(WizardProgressItem *item)
|
|
{
|
|
Q_UNUSED(item)
|
|
recreateLayout();
|
|
}
|
|
|
|
void LinearProgressWidget::slotCurrentItemChanged(WizardProgressItem *item)
|
|
{
|
|
Q_UNUSED(item)
|
|
if (m_wizardProgress->directlyReachableItems() == m_visibleItems)
|
|
updateProgress();
|
|
else
|
|
recreateLayout();
|
|
}
|
|
|
|
void LinearProgressWidget::recreateLayout()
|
|
{
|
|
disableUpdates();
|
|
|
|
QMap<WizardProgressItem *, ProgressItemWidget *>::ConstIterator it = m_itemToItemWidget.constBegin();
|
|
QMap<WizardProgressItem *, ProgressItemWidget *>::ConstIterator itEnd = m_itemToItemWidget.constEnd();
|
|
while (it != itEnd) {
|
|
it.value()->setVisible(false);
|
|
++it;
|
|
}
|
|
m_dotsItemWidget->setVisible(false);
|
|
|
|
for (int i = m_itemWidgetLayout->count() - 1; i >= 0; --i) {
|
|
QLayoutItem *item = m_itemWidgetLayout->takeAt(i);
|
|
delete item;
|
|
}
|
|
|
|
m_visibleItems = m_wizardProgress->directlyReachableItems();
|
|
for (int i = 0; i < m_visibleItems.count(); i++) {
|
|
ProgressItemWidget *itemWidget = m_itemToItemWidget.value(m_visibleItems.at(i));
|
|
m_itemWidgetLayout->addWidget(itemWidget);
|
|
itemWidget->setVisible(true);
|
|
}
|
|
|
|
if (!m_wizardProgress->isFinalItemDirectlyReachable()) {
|
|
m_itemWidgetLayout->addWidget(m_dotsItemWidget);
|
|
m_dotsItemWidget->setVisible(true);
|
|
}
|
|
|
|
enableUpdates();
|
|
updateProgress();
|
|
}
|
|
|
|
void LinearProgressWidget::updateProgress()
|
|
{
|
|
disableUpdates();
|
|
|
|
QList<WizardProgressItem *> visitedItems = m_wizardProgress->visitedItems();
|
|
|
|
QMap<WizardProgressItem *, ProgressItemWidget *>::ConstIterator it = m_itemToItemWidget.constBegin();
|
|
QMap<WizardProgressItem *, ProgressItemWidget *>::ConstIterator itEnd = m_itemToItemWidget.constEnd();
|
|
while (it != itEnd) {
|
|
WizardProgressItem *item = it.key();
|
|
ProgressItemWidget *itemWidget = it.value();
|
|
itemWidget->setEnabled(visitedItems.contains(item));
|
|
itemWidget->setIndicatorVisible(false);
|
|
++it;
|
|
}
|
|
|
|
WizardProgressItem *currentItem = m_wizardProgress->currentItem();
|
|
ProgressItemWidget *currentItemWidget = m_itemToItemWidget.value(currentItem);
|
|
if (currentItemWidget)
|
|
currentItemWidget->setIndicatorVisible(true);
|
|
|
|
enableUpdates();
|
|
}
|
|
|
|
void LinearProgressWidget::disableUpdates()
|
|
{
|
|
if (m_disableUpdatesCount++ == 0) {
|
|
setUpdatesEnabled(false);
|
|
hide();
|
|
}
|
|
}
|
|
|
|
void LinearProgressWidget::enableUpdates()
|
|
{
|
|
if (--m_disableUpdatesCount == 0) {
|
|
show();
|
|
setUpdatesEnabled(true);
|
|
}
|
|
}
|
|
|
|
class WizardPrivate
|
|
{
|
|
public:
|
|
bool m_automaticProgressCreation = true;
|
|
WizardProgress *m_wizardProgress = nullptr;
|
|
QSet<QString> m_fieldNames;
|
|
};
|
|
|
|
Wizard::Wizard(QWidget *parent, Qt::WindowFlags flags) :
|
|
QWizard(parent, flags), d_ptr(new WizardPrivate)
|
|
{
|
|
d_ptr->m_wizardProgress = new WizardProgress(this);
|
|
connect(this, &QWizard::currentIdChanged, this, &Wizard::_q_currentPageChanged);
|
|
connect(this, &QWizard::pageAdded, this, &Wizard::_q_pageAdded);
|
|
connect(this, &QWizard::pageRemoved, this, &Wizard::_q_pageRemoved);
|
|
setSideWidget(new LinearProgressWidget(d_ptr->m_wizardProgress, this));
|
|
setOption(QWizard::NoCancelButton, false);
|
|
setOption(QWizard::NoDefaultButton, false);
|
|
setOption(QWizard::NoBackButtonOnStartPage, true);
|
|
if (!Utils::creatorTheme()->preferredStyles().isEmpty())
|
|
setWizardStyle(QWizard::ModernStyle);
|
|
|
|
if (HostOsInfo::isMacHost()) {
|
|
setButtonLayout(QList<QWizard::WizardButton>()
|
|
<< QWizard::CancelButton
|
|
<< QWizard::Stretch
|
|
<< QWizard::BackButton
|
|
<< QWizard::NextButton
|
|
<< QWizard::CommitButton
|
|
<< QWizard::FinishButton);
|
|
}
|
|
}
|
|
|
|
Wizard::~Wizard()
|
|
{
|
|
delete d_ptr;
|
|
}
|
|
|
|
bool Wizard::isAutomaticProgressCreationEnabled() const
|
|
{
|
|
Q_D(const Wizard);
|
|
|
|
return d->m_automaticProgressCreation;
|
|
}
|
|
|
|
void Wizard::setAutomaticProgressCreationEnabled(bool enabled)
|
|
{
|
|
Q_D(Wizard);
|
|
|
|
d->m_automaticProgressCreation = enabled;
|
|
}
|
|
|
|
void Wizard::setStartId(int pageId)
|
|
{
|
|
Q_D(Wizard);
|
|
|
|
QWizard::setStartId(pageId);
|
|
d->m_wizardProgress->setStartPage(startId());
|
|
}
|
|
|
|
WizardProgress *Wizard::wizardProgress() const
|
|
{
|
|
Q_D(const Wizard);
|
|
|
|
return d->m_wizardProgress;
|
|
}
|
|
|
|
bool Wizard::hasField(const QString &name) const
|
|
{
|
|
return d_ptr->m_fieldNames.contains(name);
|
|
}
|
|
|
|
void Wizard::registerFieldName(const QString &name)
|
|
{
|
|
QTC_ASSERT(!hasField(name), return);
|
|
d_ptr->m_fieldNames.insert(name);
|
|
}
|
|
|
|
QSet<QString> Wizard::fieldNames() const
|
|
{
|
|
return d_ptr->m_fieldNames;
|
|
}
|
|
|
|
QHash<QString, QVariant> Wizard::variables() const
|
|
{
|
|
QHash<QString, QVariant> result;
|
|
const QSet<QString> fields = fieldNames();
|
|
for (const QString &f : fields)
|
|
result.insert(f, field(f));
|
|
return result;
|
|
}
|
|
|
|
QString typeOf(const QVariant &v)
|
|
{
|
|
QString result;
|
|
switch (v.type()) {
|
|
case QVariant::Map:
|
|
result = QLatin1String("Object");
|
|
break;
|
|
default:
|
|
result = QLatin1String(v.typeName());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Wizard::showVariables()
|
|
{
|
|
QString result = QLatin1String("<table>\n <tr><td>Key</td><td>Type</td><td>Value</td><td>Eval</td></tr>\n");
|
|
QHash<QString, QVariant> vars = variables();
|
|
QList<QString> keys = vars.keys();
|
|
sort(keys);
|
|
for (const QString &key : qAsConst(keys)) {
|
|
const QVariant &v = vars.value(key);
|
|
result += QLatin1String(" <tr><td>")
|
|
+ key + QLatin1String("</td><td>")
|
|
+ typeOf(v) + QLatin1String("</td><td>")
|
|
+ stringify(v) + QLatin1String("</td><td>")
|
|
+ evaluate(v) + QLatin1String("</td></tr>\n");
|
|
}
|
|
|
|
result += QLatin1String("</table>");
|
|
|
|
auto dialog = new QDialog(this);
|
|
dialog->setMinimumSize(800, 600);
|
|
auto layout = new QVBoxLayout(dialog);
|
|
auto scrollArea = new QScrollArea;
|
|
auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal);
|
|
|
|
auto label = new QLabel(result);
|
|
label->setWordWrap(true);
|
|
label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
|
scrollArea->setWidget(label);
|
|
scrollArea->setWidgetResizable(true);
|
|
|
|
layout->addWidget(scrollArea);
|
|
layout->addWidget(buttons);
|
|
|
|
connect(buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
|
|
connect(dialog, &QDialog::finished, dialog, &QObject::deleteLater);
|
|
|
|
dialog->show();
|
|
}
|
|
|
|
QString Wizard::stringify(const QVariant &v) const
|
|
{
|
|
return v.toString();
|
|
}
|
|
|
|
QString Wizard::evaluate(const QVariant &v) const
|
|
{
|
|
return stringify(v);
|
|
}
|
|
|
|
bool Wizard::event(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::ShortcutOverride) {
|
|
auto ke = static_cast<QKeyEvent *>(event);
|
|
if (ke->key() == Qt::Key_Escape && !ke->modifiers()) {
|
|
ke->accept();
|
|
return true;
|
|
}
|
|
}
|
|
return QWizard::event(event);
|
|
}
|
|
|
|
void Wizard::_q_currentPageChanged(int pageId)
|
|
{
|
|
Q_D(Wizard);
|
|
|
|
d->m_wizardProgress->setCurrentPage(pageId);
|
|
}
|
|
|
|
void Wizard::_q_pageAdded(int pageId)
|
|
{
|
|
Q_D(Wizard);
|
|
|
|
QWizardPage *p = page(pageId);
|
|
auto wp = qobject_cast<WizardPage *>(p);
|
|
if (wp)
|
|
wp->pageWasAdded();
|
|
|
|
if (!d->m_automaticProgressCreation)
|
|
return;
|
|
|
|
QVariant property = p->property(SHORT_TITLE_PROPERTY);
|
|
const QString title = property.isNull() ? p->title() : property.toString();
|
|
WizardProgressItem *item = d->m_wizardProgress->addItem(title);
|
|
item->addPage(pageId);
|
|
d->m_wizardProgress->setStartPage(startId());
|
|
if (!d->m_wizardProgress->startItem())
|
|
return;
|
|
|
|
QList<int> pages = pageIds();
|
|
int index = pages.indexOf(pageId);
|
|
int prevId = -1;
|
|
int nextId = -1;
|
|
if (index > 0)
|
|
prevId = pages.at(index - 1);
|
|
if (index < pages.count() - 1)
|
|
nextId = pages.at(index + 1);
|
|
|
|
WizardProgressItem *prevItem = nullptr;
|
|
WizardProgressItem *nextItem = nullptr;
|
|
|
|
if (prevId >= 0)
|
|
prevItem = d->m_wizardProgress->item(prevId);
|
|
if (nextId >= 0)
|
|
nextItem = d->m_wizardProgress->item(nextId);
|
|
|
|
if (prevItem)
|
|
prevItem->setNextItems({item});
|
|
if (nextItem)
|
|
item->setNextItems({nextItem});
|
|
}
|
|
|
|
void Wizard::_q_pageRemoved(int pageId)
|
|
{
|
|
Q_D(Wizard);
|
|
|
|
if (!d->m_automaticProgressCreation)
|
|
return;
|
|
|
|
WizardProgressItem *item = d->m_wizardProgress->item(pageId);
|
|
d->m_wizardProgress->removePage(pageId);
|
|
d->m_wizardProgress->setStartPage(startId());
|
|
|
|
if (!item->pages().isEmpty())
|
|
return;
|
|
|
|
QList<int> pages = pageIds();
|
|
int index = pages.indexOf(pageId);
|
|
int prevId = -1;
|
|
int nextId = -1;
|
|
if (index > 0)
|
|
prevId = pages.at(index - 1);
|
|
if (index < pages.count() - 1)
|
|
nextId = pages.at(index + 1);
|
|
|
|
WizardProgressItem *prevItem = nullptr;
|
|
WizardProgressItem *nextItem = nullptr;
|
|
|
|
if (prevId >= 0)
|
|
prevItem = d->m_wizardProgress->item(prevId);
|
|
if (nextId >= 0)
|
|
nextItem = d->m_wizardProgress->item(nextId);
|
|
|
|
if (prevItem && nextItem) {
|
|
QList<WizardProgressItem *> nextItems = prevItem->nextItems();
|
|
nextItems.removeOne(item);
|
|
if (!nextItems.contains(nextItem))
|
|
nextItems.append(nextItem);
|
|
prevItem->setNextItems(nextItems);
|
|
}
|
|
d->m_wizardProgress->removeItem(item);
|
|
}
|
|
|
|
|
|
|
|
|
|
class WizardProgressPrivate
|
|
{
|
|
WizardProgress *q_ptr;
|
|
Q_DECLARE_PUBLIC(WizardProgress)
|
|
|
|
public:
|
|
WizardProgressPrivate() = default;
|
|
|
|
static bool isNextItem(WizardProgressItem *item, WizardProgressItem *nextItem);
|
|
// if multiple paths are possible the empty list is returned
|
|
QList<WizardProgressItem *> singlePathBetween(WizardProgressItem *fromItem, WizardProgressItem *toItem) const;
|
|
void updateReachableItems();
|
|
|
|
QMap<int, WizardProgressItem *> m_pageToItem;
|
|
QMap<WizardProgressItem *, WizardProgressItem *> m_itemToItem;
|
|
|
|
QList<WizardProgressItem *> m_items;
|
|
|
|
QList<WizardProgressItem *> m_visitedItems;
|
|
QList<WizardProgressItem *> m_reachableItems;
|
|
|
|
WizardProgressItem *m_currentItem = nullptr;
|
|
WizardProgressItem *m_startItem = nullptr;
|
|
};
|
|
|
|
class WizardProgressItemPrivate
|
|
{
|
|
WizardProgressItem *q_ptr;
|
|
Q_DECLARE_PUBLIC(WizardProgressItem)
|
|
public:
|
|
QString m_title;
|
|
bool m_titleWordWrap;
|
|
WizardProgress *m_wizardProgress;
|
|
QList<int> m_pages;
|
|
QList<WizardProgressItem *> m_nextItems;
|
|
QList<WizardProgressItem *> m_prevItems;
|
|
WizardProgressItem *m_nextShownItem;
|
|
};
|
|
|
|
bool WizardProgressPrivate::isNextItem(WizardProgressItem *item, WizardProgressItem *nextItem)
|
|
{
|
|
QHash<WizardProgressItem *, bool> visitedItems;
|
|
QList<WizardProgressItem *> workingItems = item->nextItems();
|
|
while (!workingItems.isEmpty()) {
|
|
WizardProgressItem *workingItem = workingItems.takeFirst();
|
|
if (workingItem == nextItem)
|
|
return true;
|
|
if (visitedItems.contains(workingItem))
|
|
continue;
|
|
visitedItems.insert(workingItem, true);
|
|
workingItems += workingItem->nextItems();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QList<WizardProgressItem *> WizardProgressPrivate::singlePathBetween(WizardProgressItem *fromItem, WizardProgressItem *toItem) const
|
|
{
|
|
WizardProgressItem *item = fromItem;
|
|
if (!item)
|
|
item = m_startItem;
|
|
if (!item)
|
|
return QList<WizardProgressItem *>();
|
|
|
|
// Optimization. It is workaround for case A->B, B->C, A->C where "from" is A and "to" is C.
|
|
// When we had X->A in addition and "from" was X and "to" was C, this would not work
|
|
// (it should return the shortest path which would be X->A->C).
|
|
if (item->nextItems().contains(toItem))
|
|
return {toItem};
|
|
|
|
QHash<WizardProgressItem *, QHash<WizardProgressItem *, bool> > visitedItemsToParents;
|
|
QList<QPair<WizardProgressItem *, WizardProgressItem *> > workingItems; // next to prev item
|
|
|
|
QList<WizardProgressItem *> items = item->nextItems();
|
|
for (int i = 0; i < items.count(); i++)
|
|
workingItems.append(qMakePair(items.at(i), item));
|
|
|
|
while (!workingItems.isEmpty()) {
|
|
QPair<WizardProgressItem *, WizardProgressItem *> workingItem = workingItems.takeFirst();
|
|
|
|
QHash<WizardProgressItem *, bool> &parents = visitedItemsToParents[workingItem.first];
|
|
parents.insert(workingItem.second, true);
|
|
if (parents.count() > 1)
|
|
continue;
|
|
|
|
QList<WizardProgressItem *> items = workingItem.first->nextItems();
|
|
for (int i = 0; i < items.count(); i++)
|
|
workingItems.append(qMakePair(items.at(i), workingItem.first));
|
|
}
|
|
|
|
QList<WizardProgressItem *> path;
|
|
|
|
WizardProgressItem *it = toItem;
|
|
QHash<WizardProgressItem *, QHash<WizardProgressItem *, bool> >::ConstIterator itItem = visitedItemsToParents.constFind(it);
|
|
QHash<WizardProgressItem *, QHash<WizardProgressItem *, bool> >::ConstIterator itEnd = visitedItemsToParents.constEnd();
|
|
while (itItem != itEnd) {
|
|
path.prepend(itItem.key());
|
|
if (itItem.value().count() != 1)
|
|
return QList<WizardProgressItem *>();
|
|
it = itItem.value().constBegin().key();
|
|
if (it == item)
|
|
return path;
|
|
itItem = visitedItemsToParents.constFind(it);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void WizardProgressPrivate::updateReachableItems()
|
|
{
|
|
m_reachableItems = m_visitedItems;
|
|
WizardProgressItem *item = nullptr;
|
|
if (m_visitedItems.count() > 0)
|
|
item = m_visitedItems.last();
|
|
if (!item) {
|
|
item = m_startItem;
|
|
m_reachableItems.append(item);
|
|
}
|
|
if (!item)
|
|
return;
|
|
while (item->nextShownItem()) {
|
|
item = item->nextShownItem();
|
|
m_reachableItems.append(item);
|
|
}
|
|
}
|
|
|
|
|
|
WizardProgress::WizardProgress(QObject *parent)
|
|
: QObject(parent), d_ptr(new WizardProgressPrivate)
|
|
{
|
|
d_ptr->q_ptr = this;
|
|
}
|
|
|
|
WizardProgress::~WizardProgress()
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
QMap<WizardProgressItem *, WizardProgressItem *>::ConstIterator it = d->m_itemToItem.constBegin();
|
|
QMap<WizardProgressItem *, WizardProgressItem *>::ConstIterator itEnd = d->m_itemToItem.constEnd();
|
|
while (it != itEnd) {
|
|
delete it.key();
|
|
++it;
|
|
}
|
|
delete d_ptr;
|
|
}
|
|
|
|
WizardProgressItem *WizardProgress::addItem(const QString &title)
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
auto item = new WizardProgressItem(this, title);
|
|
d->m_itemToItem.insert(item, item);
|
|
emit itemAdded(item);
|
|
return item;
|
|
}
|
|
|
|
void WizardProgress::removeItem(WizardProgressItem *item)
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
QMap<WizardProgressItem *, WizardProgressItem *>::iterator it = d->m_itemToItem.find(item);
|
|
if (it == d->m_itemToItem.end()) {
|
|
qWarning("WizardProgress::removePage: Item is not a part of the wizard");
|
|
return;
|
|
}
|
|
|
|
// remove item from prev items
|
|
QList<WizardProgressItem *> prevItems = item->d_ptr->m_prevItems;
|
|
for (int i = 0; i < prevItems.count(); i++) {
|
|
WizardProgressItem *prevItem = prevItems.at(i);
|
|
prevItem->d_ptr->m_nextItems.removeOne(item);
|
|
}
|
|
|
|
// remove item from next items
|
|
QList<WizardProgressItem *> nextItems = item->d_ptr->m_nextItems;
|
|
for (int i = 0; i < nextItems.count(); i++) {
|
|
WizardProgressItem *nextItem = nextItems.at(i);
|
|
nextItem->d_ptr->m_prevItems.removeOne(item);
|
|
}
|
|
|
|
// update history
|
|
int idx = d->m_visitedItems.indexOf(item);
|
|
if (idx >= 0)
|
|
d->m_visitedItems.removeAt(idx);
|
|
|
|
// update reachable items
|
|
d->updateReachableItems();
|
|
|
|
emit itemRemoved(item);
|
|
|
|
QList<int> pages = item->pages();
|
|
for (int i = 0; i < pages.count(); i++)
|
|
d->m_pageToItem.remove(pages.at(i));
|
|
d->m_itemToItem.erase(it);
|
|
delete item;
|
|
}
|
|
|
|
void WizardProgress::removePage(int pageId)
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
QMap<int, WizardProgressItem *>::iterator it = d->m_pageToItem.find(pageId);
|
|
if (it == d->m_pageToItem.end()) {
|
|
qWarning("WizardProgress::removePage: page is not a part of the wizard");
|
|
return;
|
|
}
|
|
WizardProgressItem *item = it.value();
|
|
d->m_pageToItem.erase(it);
|
|
item->d_ptr->m_pages.removeOne(pageId);
|
|
}
|
|
|
|
QList<int> WizardProgress::pages(WizardProgressItem *item)
|
|
{
|
|
return item->pages();
|
|
}
|
|
|
|
WizardProgressItem *WizardProgress::item(int pageId) const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_pageToItem.value(pageId);
|
|
}
|
|
|
|
WizardProgressItem *WizardProgress::currentItem() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_currentItem;
|
|
}
|
|
|
|
QList<WizardProgressItem *> WizardProgress::items() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_itemToItem.keys();
|
|
}
|
|
|
|
WizardProgressItem *WizardProgress::startItem() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_startItem;
|
|
}
|
|
|
|
QList<WizardProgressItem *> WizardProgress::visitedItems() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_visitedItems;
|
|
}
|
|
|
|
QList<WizardProgressItem *> WizardProgress::directlyReachableItems() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
return d->m_reachableItems;
|
|
}
|
|
|
|
bool WizardProgress::isFinalItemDirectlyReachable() const
|
|
{
|
|
Q_D(const WizardProgress);
|
|
|
|
if (d->m_reachableItems.isEmpty())
|
|
return false;
|
|
|
|
return d->m_reachableItems.last()->isFinalItem();
|
|
}
|
|
|
|
void WizardProgress::setCurrentPage(int pageId)
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
if (pageId < 0) { // reset history
|
|
d->m_currentItem = nullptr;
|
|
d->m_visitedItems.clear();
|
|
d->m_reachableItems.clear();
|
|
d->updateReachableItems();
|
|
return;
|
|
}
|
|
|
|
WizardProgressItem *item = d->m_pageToItem.value(pageId);
|
|
if (!item) {
|
|
qWarning("WizardProgress::setCurrentPage: page is not mapped to any wizard progress item");
|
|
return;
|
|
}
|
|
|
|
if (d->m_currentItem == item) // nothing changes
|
|
return;
|
|
|
|
const bool currentStartItem = !d->m_currentItem && d->m_startItem && d->m_startItem == item;
|
|
|
|
// Check if item is reachable with the provided history or with the next items.
|
|
const QList<WizardProgressItem *> singleItemPath = d->singlePathBetween(d->m_currentItem, item);
|
|
const int prevItemIndex = d->m_visitedItems.indexOf(item);
|
|
|
|
if (singleItemPath.isEmpty() && prevItemIndex < 0 && !currentStartItem) {
|
|
qWarning("WizardProgress::setCurrentPage: new current item is not directly reachable from the old current item");
|
|
return;
|
|
}
|
|
|
|
// Update the history accordingly.
|
|
if (prevItemIndex >= 0) {
|
|
while (prevItemIndex + 1 < d->m_visitedItems.count())
|
|
d->m_visitedItems.removeLast();
|
|
} else {
|
|
if ((!d->m_currentItem && d->m_startItem && !singleItemPath.isEmpty()) || currentStartItem)
|
|
d->m_visitedItems += d->m_startItem;
|
|
d->m_visitedItems += singleItemPath;
|
|
}
|
|
|
|
d->m_currentItem = item;
|
|
|
|
// Update reachable items accordingly.
|
|
d->updateReachableItems();
|
|
|
|
emit currentItemChanged(item);
|
|
}
|
|
|
|
void WizardProgress::setStartPage(int pageId)
|
|
{
|
|
Q_D(WizardProgress);
|
|
|
|
WizardProgressItem *item = d->m_pageToItem.value(pageId);
|
|
if (!item) {
|
|
qWarning("WizardProgress::setStartPage: page is not mapped to any wizard progress item");
|
|
return;
|
|
}
|
|
|
|
d->m_startItem = item;
|
|
d->updateReachableItems();
|
|
|
|
emit startItemChanged(item);
|
|
}
|
|
|
|
WizardProgressItem::WizardProgressItem(WizardProgress *progress, const QString &title)
|
|
: d_ptr(new WizardProgressItemPrivate)
|
|
{
|
|
d_ptr->q_ptr = this;
|
|
d_ptr->m_title = title;
|
|
d_ptr->m_titleWordWrap = false;
|
|
d_ptr->m_wizardProgress = progress;
|
|
d_ptr->m_nextShownItem = nullptr;
|
|
}
|
|
|
|
WizardProgressItem::~WizardProgressItem()
|
|
{
|
|
delete d_ptr;
|
|
}
|
|
|
|
void WizardProgressItem::addPage(int pageId)
|
|
{
|
|
Q_D(WizardProgressItem);
|
|
|
|
if (d->m_wizardProgress->d_ptr->m_pageToItem.contains(pageId)) {
|
|
qWarning("WizardProgress::addPage: Page is already added to the item");
|
|
return;
|
|
}
|
|
d->m_pages.append(pageId);
|
|
d->m_wizardProgress->d_ptr->m_pageToItem.insert(pageId, this);
|
|
}
|
|
|
|
QList<int> WizardProgressItem::pages() const
|
|
{
|
|
Q_D(const WizardProgressItem);
|
|
|
|
return d->m_pages;
|
|
}
|
|
|
|
void WizardProgressItem::setNextItems(const QList<WizardProgressItem *> &items)
|
|
{
|
|
Q_D(WizardProgressItem);
|
|
|
|
// check if we create cycle
|
|
for (int i = 0; i < items.count(); i++) {
|
|
WizardProgressItem *nextItem = items.at(i);
|
|
if (nextItem == this || WizardProgressPrivate::isNextItem(nextItem, this)) {
|
|
qWarning("WizardProgress::setNextItems: Setting one of the next items would create a cycle");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (d->m_nextItems == items) // nothing changes
|
|
return;
|
|
|
|
if (!items.contains(d->m_nextShownItem))
|
|
setNextShownItem(nullptr);
|
|
|
|
// update prev items (remove this item from the old next items)
|
|
for (int i = 0; i < d->m_nextItems.count(); i++) {
|
|
WizardProgressItem *nextItem = d->m_nextItems.at(i);
|
|
nextItem->d_ptr->m_prevItems.removeOne(this);
|
|
}
|
|
|
|
d->m_nextItems = items;
|
|
|
|
// update prev items (add this item to the new next items)
|
|
for (int i = 0; i < d->m_nextItems.count(); i++) {
|
|
WizardProgressItem *nextItem = d->m_nextItems.at(i);
|
|
nextItem->d_ptr->m_prevItems.append(this);
|
|
}
|
|
|
|
d->m_wizardProgress->d_ptr->updateReachableItems();
|
|
|
|
emit d->m_wizardProgress->nextItemsChanged(this, items);
|
|
|
|
if (items.count() == 1)
|
|
setNextShownItem(items.first());
|
|
}
|
|
|
|
QList<WizardProgressItem *> WizardProgressItem::nextItems() const
|
|
{
|
|
Q_D(const WizardProgressItem);
|
|
|
|
return d->m_nextItems;
|
|
}
|
|
|
|
void WizardProgressItem::setNextShownItem(WizardProgressItem *item)
|
|
{
|
|
Q_D(WizardProgressItem);
|
|
|
|
if (d->m_nextShownItem == item) // nothing changes
|
|
return;
|
|
|
|
if (item && !d->m_nextItems.contains(item)) // the "item" is not a one of next items
|
|
return;
|
|
|
|
d->m_nextShownItem = item;
|
|
|
|
d->m_wizardProgress->d_ptr->updateReachableItems();
|
|
|
|
emit d->m_wizardProgress->nextShownItemChanged(this, item);
|
|
}
|
|
|
|
WizardProgressItem *WizardProgressItem::nextShownItem() const
|
|
{
|
|
Q_D(const WizardProgressItem);
|
|
|
|
return d->m_nextShownItem;
|
|
}
|
|
|
|
bool WizardProgressItem::isFinalItem() const
|
|
{
|
|
return nextItems().isEmpty();
|
|
}
|
|
|
|
void WizardProgressItem::setTitle(const QString &title)
|
|
{
|
|
Q_D(WizardProgressItem);
|
|
|
|
d->m_title = title;
|
|
emit d->m_wizardProgress->itemChanged(this);
|
|
}
|
|
|
|
QString WizardProgressItem::title() const
|
|
{
|
|
Q_D(const WizardProgressItem);
|
|
|
|
return d->m_title;
|
|
}
|
|
|
|
void WizardProgressItem::setTitleWordWrap(bool wrap)
|
|
{
|
|
Q_D(WizardProgressItem);
|
|
|
|
d->m_titleWordWrap = wrap;
|
|
emit d->m_wizardProgress->itemChanged(this);
|
|
}
|
|
|
|
bool WizardProgressItem::titleWordWrap() const
|
|
{
|
|
Q_D(const WizardProgressItem);
|
|
|
|
return d->m_titleWordWrap;
|
|
}
|
|
|
|
} // namespace Utils
|
|
|
|
#include "wizard.moc"
|