/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWidgets module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qwizard.h" #ifndef QT_NO_WIZARD #include "qabstractspinbox.h" #include "qalgorithms.h" #include "qapplication.h" #include "qboxlayout.h" #include "qlayoutitem.h" #include "qdesktopwidget.h" #include "qevent.h" #include "qframe.h" #include "qlabel.h" #include "qlineedit.h" #include "qpainter.h" #include "qwindow.h" #include "qpushbutton.h" #include "qset.h" #include "qstyle.h" #include "qvarlengtharray.h" #if defined(Q_OS_MACX) #include #include #include #elif !defined(QT_NO_STYLE_WINDOWSVISTA) #include "qwizard_win_p.h" #include "qtimer.h" #endif #include "private/qdialog_p.h" #include #ifdef Q_OS_WINCE extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp #endif #include // for memset() QT_BEGIN_NAMESPACE // These fudge terms were needed a few places to obtain pixel-perfect results const int GapBetweenLogoAndRightEdge = 5; const int ModernHeaderTopMargin = 2; const int ClassicHMargin = 4; const int MacButtonTopMargin = 13; const int MacLayoutLeftMargin = 20; //const int MacLayoutTopMargin = 14; // Unused. Save some space and avoid warning. const int MacLayoutRightMargin = 20; const int MacLayoutBottomMargin = 17; static void changeSpacerSize(QLayout *layout, int index, int width, int height) { QSpacerItem *spacer = layout->itemAt(index)->spacerItem(); if (!spacer) return; spacer->changeSize(width, height); } static QWidget *iWantTheFocus(QWidget *ancestor) { const int MaxIterations = 100; QWidget *candidate = ancestor; for (int i = 0; i < MaxIterations; ++i) { candidate = candidate->nextInFocusChain(); if (!candidate) break; if (candidate->focusPolicy() & Qt::TabFocus) { if (candidate != ancestor && ancestor->isAncestorOf(candidate)) return candidate; } } return 0; } static bool objectInheritsXAndXIsCloserThanY(const QObject *object, const QByteArray &classX, const QByteArray &classY) { const QMetaObject *metaObject = object->metaObject(); while (metaObject) { if (metaObject->className() == classX) return true; if (metaObject->className() == classY) return false; metaObject = metaObject->superClass(); } return false; } const int NFallbackDefaultProperties = 7; const struct { const char *className; const char *property; const char *changedSignal; } fallbackProperties[NFallbackDefaultProperties] = { // If you modify this list, make sure to update the documentation (and the auto test) { "QAbstractButton", "checked", SIGNAL(toggled(bool)) }, { "QAbstractSlider", "value", SIGNAL(valueChanged(int)) }, { "QComboBox", "currentIndex", SIGNAL(currentIndexChanged(int)) }, { "QDateTimeEdit", "dateTime", SIGNAL(dateTimeChanged(QDateTime)) }, { "QLineEdit", "text", SIGNAL(textChanged(QString)) }, { "QListWidget", "currentRow", SIGNAL(currentRowChanged(int)) }, { "QSpinBox", "value", SIGNAL(valueChanged(int)) } }; class QWizardDefaultProperty { public: QByteArray className; QByteArray property; QByteArray changedSignal; inline QWizardDefaultProperty() {} inline QWizardDefaultProperty(const char *className, const char *property, const char *changedSignal) : className(className), property(property), changedSignal(changedSignal) {} }; class QWizardField { public: inline QWizardField() {} QWizardField(QWizardPage *page, const QString &spec, QObject *object, const char *property, const char *changedSignal); void resolve(const QVector &defaultPropertyTable); void findProperty(const QWizardDefaultProperty *properties, int propertyCount); QWizardPage *page; QString name; bool mandatory; QObject *object; QByteArray property; QByteArray changedSignal; QVariant initialValue; }; QWizardField::QWizardField(QWizardPage *page, const QString &spec, QObject *object, const char *property, const char *changedSignal) : page(page), name(spec), mandatory(false), object(object), property(property), changedSignal(changedSignal) { if (name.endsWith(QLatin1Char('*'))) { name.chop(1); mandatory = true; } } void QWizardField::resolve(const QVector &defaultPropertyTable) { if (property.isEmpty()) findProperty(defaultPropertyTable.constData(), defaultPropertyTable.count()); initialValue = object->property(property); } void QWizardField::findProperty(const QWizardDefaultProperty *properties, int propertyCount) { QByteArray className; for (int i = 0; i < propertyCount; ++i) { if (objectInheritsXAndXIsCloserThanY(object, properties[i].className, className)) { className = properties[i].className; property = properties[i].property; changedSignal = properties[i].changedSignal; } } } class QWizardLayoutInfo { public: inline QWizardLayoutInfo() : topLevelMarginLeft(-1), topLevelMarginRight(-1), topLevelMarginTop(-1), topLevelMarginBottom(-1), childMarginLeft(-1), childMarginRight(-1), childMarginTop(-1), childMarginBottom(-1), hspacing(-1), vspacing(-1), wizStyle(QWizard::ClassicStyle), header(false), watermark(false), title(false), subTitle(false), extension(false), sideWidget(false) {} int topLevelMarginLeft; int topLevelMarginRight; int topLevelMarginTop; int topLevelMarginBottom; int childMarginLeft; int childMarginRight; int childMarginTop; int childMarginBottom; int hspacing; int vspacing; int buttonSpacing; QWizard::WizardStyle wizStyle; bool header; bool watermark; bool title; bool subTitle; bool extension; bool sideWidget; bool operator==(const QWizardLayoutInfo &other); inline bool operator!=(const QWizardLayoutInfo &other) { return !operator==(other); } }; bool QWizardLayoutInfo::operator==(const QWizardLayoutInfo &other) { return topLevelMarginLeft == other.topLevelMarginLeft && topLevelMarginRight == other.topLevelMarginRight && topLevelMarginTop == other.topLevelMarginTop && topLevelMarginBottom == other.topLevelMarginBottom && childMarginLeft == other.childMarginLeft && childMarginRight == other.childMarginRight && childMarginTop == other.childMarginTop && childMarginBottom == other.childMarginBottom && hspacing == other.hspacing && vspacing == other.vspacing && buttonSpacing == other.buttonSpacing && wizStyle == other.wizStyle && header == other.header && watermark == other.watermark && title == other.title && subTitle == other.subTitle && extension == other.extension && sideWidget == other.sideWidget; } class QWizardHeader : public QWidget { public: enum RulerType { Ruler }; inline QWizardHeader(RulerType /* ruler */, QWidget *parent = 0) : QWidget(parent) { setFixedHeight(2); } QWizardHeader(QWidget *parent = 0); void setup(const QWizardLayoutInfo &info, const QString &title, const QString &subTitle, const QPixmap &logo, const QPixmap &banner, Qt::TextFormat titleFormat, Qt::TextFormat subTitleFormat); protected: void paintEvent(QPaintEvent *event); #if !defined(QT_NO_STYLE_WINDOWSVISTA) private: bool vistaDisabled() const; #endif private: QLabel *titleLabel; QLabel *subTitleLabel; QLabel *logoLabel; QGridLayout *layout; QPixmap bannerPixmap; }; QWizardHeader::QWizardHeader(QWidget *parent) : QWidget(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setBackgroundRole(QPalette::Base); titleLabel = new QLabel(this); titleLabel->setBackgroundRole(QPalette::Base); subTitleLabel = new QLabel(this); subTitleLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); subTitleLabel->setWordWrap(true); logoLabel = new QLabel(this); QFont font = titleLabel->font(); font.setBold(true); titleLabel->setFont(font); layout = new QGridLayout(this); layout->setMargin(0); layout->setSpacing(0); layout->setRowMinimumHeight(3, 1); layout->setRowStretch(4, 1); layout->setColumnStretch(2, 1); layout->setColumnMinimumWidth(4, 2 * GapBetweenLogoAndRightEdge); layout->setColumnMinimumWidth(6, GapBetweenLogoAndRightEdge); layout->addWidget(titleLabel, 2, 1, 1, 2); layout->addWidget(subTitleLabel, 4, 2); layout->addWidget(logoLabel, 1, 5, 5, 1); } #if !defined(QT_NO_STYLE_WINDOWSVISTA) bool QWizardHeader::vistaDisabled() const { bool styleDisabled = false; QWizard *wiz = parentWidget() ? qobject_cast (parentWidget()->parentWidget()) : 0; if (wiz) { // Designer dosen't support the Vista style for Wizards. This property is used to turn // off the Vista style. const QVariant v = wiz->property("_q_wizard_vista_off"); styleDisabled = v.isValid() && v.toBool(); } return styleDisabled; } #endif void QWizardHeader::setup(const QWizardLayoutInfo &info, const QString &title, const QString &subTitle, const QPixmap &logo, const QPixmap &banner, Qt::TextFormat titleFormat, Qt::TextFormat subTitleFormat) { bool modern = ((info.wizStyle == QWizard::ModernStyle) #if !defined(QT_NO_STYLE_WINDOWSVISTA) || ((info.wizStyle == QWizard::AeroStyle && QVistaHelper::vistaState() == QVistaHelper::Classic) || vistaDisabled()) #endif ); layout->setRowMinimumHeight(0, modern ? ModernHeaderTopMargin : 0); layout->setRowMinimumHeight(1, modern ? info.topLevelMarginTop - ModernHeaderTopMargin - 1 : 0); layout->setRowMinimumHeight(6, (modern ? 3 : GapBetweenLogoAndRightEdge) + 2); int minColumnWidth0 = modern ? info.topLevelMarginLeft + info.topLevelMarginRight : 0; int minColumnWidth1 = modern ? info.topLevelMarginLeft + info.topLevelMarginRight + 1 : info.topLevelMarginLeft + ClassicHMargin; layout->setColumnMinimumWidth(0, minColumnWidth0); layout->setColumnMinimumWidth(1, minColumnWidth1); titleLabel->setTextFormat(titleFormat); titleLabel->setText(title); logoLabel->setPixmap(logo); subTitleLabel->setTextFormat(subTitleFormat); subTitleLabel->setText(QLatin1String("Pq\nPq")); int desiredSubTitleHeight = subTitleLabel->sizeHint().height(); subTitleLabel->setText(subTitle); if (modern) { bannerPixmap = banner; } else { bannerPixmap = QPixmap(); } if (bannerPixmap.isNull()) { /* There is no widthForHeight() function, so we simulate it with a loop. */ int candidateSubTitleWidth = qMin(512, 2 * QApplication::desktop()->width() / 3); int delta = candidateSubTitleWidth >> 1; while (delta > 0) { if (subTitleLabel->heightForWidth(candidateSubTitleWidth - delta) <= desiredSubTitleHeight) candidateSubTitleWidth -= delta; delta >>= 1; } subTitleLabel->setMinimumSize(candidateSubTitleWidth, desiredSubTitleHeight); QSize size = layout->totalMinimumSize(); setMinimumSize(size); setMaximumSize(QWIDGETSIZE_MAX, size.height()); } else { subTitleLabel->setMinimumSize(0, 0); setFixedSize(banner.size() + QSize(0, 2)); } updateGeometry(); } void QWizardHeader::paintEvent(QPaintEvent * /* event */) { QPainter painter(this); painter.drawPixmap(0, 0, bannerPixmap); int x = width() - 2; int y = height() - 2; const QPalette &pal = palette(); painter.setPen(pal.mid().color()); painter.drawLine(0, y, x, y); painter.setPen(pal.base().color()); painter.drawPoint(x + 1, y); painter.drawLine(0, y + 1, x + 1, y + 1); } // We save one vtable by basing QWizardRuler on QWizardHeader class QWizardRuler : public QWizardHeader { public: inline QWizardRuler(QWidget *parent = 0) : QWizardHeader(Ruler, parent) {} }; class QWatermarkLabel : public QLabel { public: QWatermarkLabel(QWidget *parent, QWidget *sideWidget) : QLabel(parent), m_sideWidget(sideWidget) { m_layout = new QVBoxLayout(this); if (m_sideWidget) m_layout->addWidget(m_sideWidget); } QSize minimumSizeHint() const { if (!pixmap() && !pixmap()->isNull()) return pixmap()->size(); return QFrame::minimumSizeHint(); } void setSideWidget(QWidget *widget) { if (m_sideWidget == widget) return; if (m_sideWidget) { m_layout->removeWidget(m_sideWidget); m_sideWidget->hide(); } m_sideWidget = widget; if (m_sideWidget) m_layout->addWidget(m_sideWidget); } QWidget *sideWidget() const { return m_sideWidget; } private: QVBoxLayout *m_layout; QWidget *m_sideWidget; }; class QWizardPagePrivate : public QWidgetPrivate { Q_DECLARE_PUBLIC(QWizardPage) public: enum TriState { Tri_Unknown = -1, Tri_False, Tri_True }; inline QWizardPagePrivate() : wizard(0), completeState(Tri_Unknown), explicitlyFinal(false), commit(false) {} bool cachedIsComplete() const; void _q_maybeEmitCompleteChanged(); void _q_updateCachedCompleteState(); QWizard *wizard; QString title; QString subTitle; QPixmap pixmaps[QWizard::NPixmaps]; QVector pendingFields; mutable TriState completeState; bool explicitlyFinal; bool commit; QMap buttonCustomTexts; }; bool QWizardPagePrivate::cachedIsComplete() const { Q_Q(const QWizardPage); if (completeState == Tri_Unknown) completeState = q->isComplete() ? Tri_True : Tri_False; return completeState == Tri_True; } void QWizardPagePrivate::_q_maybeEmitCompleteChanged() { Q_Q(QWizardPage); TriState newState = q->isComplete() ? Tri_True : Tri_False; if (newState != completeState) emit q->completeChanged(); } void QWizardPagePrivate::_q_updateCachedCompleteState() { Q_Q(QWizardPage); completeState = q->isComplete() ? Tri_True : Tri_False; } class QWizardAntiFlickerWidget : public QWidget { public: #if !defined(QT_NO_STYLE_WINDOWSVISTA) QWizardPrivate *wizardPrivate; QWizardAntiFlickerWidget(QWizard *wizard, QWizardPrivate *wizardPrivate) : QWidget(wizard) , wizardPrivate(wizardPrivate) {} protected: void paintEvent(QPaintEvent *); #else QWizardAntiFlickerWidget(QWizard *wizard, QWizardPrivate *) : QWidget(wizard) {} #endif }; class QWizardPrivate : public QDialogPrivate { Q_DECLARE_PUBLIC(QWizard) public: typedef QMap PageMap; enum Direction { Backward, Forward }; inline QWizardPrivate() : start(-1) , startSetByUser(false) , current(-1) , canContinue(false) , canFinish(false) , disableUpdatesCount(0) , opts(0) , buttonsHaveCustomLayout(false) , titleFmt(Qt::AutoText) , subTitleFmt(Qt::AutoText) , placeholderWidget1(0) , placeholderWidget2(0) , headerWidget(0) , watermarkLabel(0) , sideWidget(0) , titleLabel(0) , subTitleLabel(0) , bottomRuler(0) #if !defined(QT_NO_STYLE_WINDOWSVISTA) , vistaInitPending(false) , vistaState(QVistaHelper::Dirty) , vistaStateChanged(false) , inHandleAeroStyleChange(false) #endif , minimumWidth(0) , minimumHeight(0) , maximumWidth(QWIDGETSIZE_MAX) , maximumHeight(QWIDGETSIZE_MAX) { for (int i = 0; i < QWizard::NButtons; ++i) btns[i] = 0; #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based)) vistaInitPending = true; #endif } void init(); void reset(); void cleanupPagesNotInHistory(); void addField(const QWizardField &field); void removeFieldAt(int index); void switchToPage(int newId, Direction direction); QWizardLayoutInfo layoutInfoForCurrentPage(); void recreateLayout(const QWizardLayoutInfo &info); void updateLayout(); void updateMinMaxSizes(const QWizardLayoutInfo &info); void updateCurrentPage(); bool ensureButton(QWizard::WizardButton which) const; void connectButton(QWizard::WizardButton which) const; void updateButtonTexts(); void updateButtonLayout(); void setButtonLayout(const QWizard::WizardButton *array, int size); bool buttonLayoutContains(QWizard::WizardButton which); void updatePixmap(QWizard::WizardPixmap which); #if !defined(QT_NO_STYLE_WINDOWSVISTA) bool vistaDisabled() const; bool isVistaThemeEnabled(QVistaHelper::VistaState state) const; bool handleAeroStyleChange(); #endif bool isVistaThemeEnabled() const; void disableUpdates(); void enableUpdates(); void _q_emitCustomButtonClicked(); void _q_updateButtonStates(); void _q_handleFieldObjectDestroyed(QObject *); void setStyle(QStyle *style); #ifdef Q_OS_MACX static QPixmap findDefaultBackgroundPixmap(); #endif PageMap pageMap; QVector fields; QMap fieldIndexMap; QVector defaultPropertyTable; QList history; QSet initialized; // ### remove and move bit to QWizardPage? int start; bool startSetByUser; int current; bool canContinue; bool canFinish; QWizardLayoutInfo layoutInfo; int disableUpdatesCount; QWizard::WizardStyle wizStyle; QWizard::WizardOptions opts; QMap buttonCustomTexts; bool buttonsHaveCustomLayout; QList buttonsCustomLayout; Qt::TextFormat titleFmt; Qt::TextFormat subTitleFmt; mutable QPixmap defaultPixmaps[QWizard::NPixmaps]; union { // keep in sync with QWizard::WizardButton mutable struct { QAbstractButton *back; QAbstractButton *next; QAbstractButton *commit; QAbstractButton *finish; QAbstractButton *cancel; QAbstractButton *help; } btn; mutable QAbstractButton *btns[QWizard::NButtons]; }; QWizardAntiFlickerWidget *antiFlickerWidget; QWidget *placeholderWidget1; QWidget *placeholderWidget2; QWizardHeader *headerWidget; QWatermarkLabel *watermarkLabel; QWidget *sideWidget; QFrame *pageFrame; QLabel *titleLabel; QLabel *subTitleLabel; QWizardRuler *bottomRuler; QVBoxLayout *pageVBoxLayout; QHBoxLayout *buttonLayout; QGridLayout *mainLayout; #if !defined(QT_NO_STYLE_WINDOWSVISTA) QVistaHelper *vistaHelper; bool vistaInitPending; QVistaHelper::VistaState vistaState; bool vistaStateChanged; bool inHandleAeroStyleChange; #endif int minimumWidth; int minimumHeight; int maximumWidth; int maximumHeight; }; static QString buttonDefaultText(int wstyle, int which, const QWizardPrivate *wizardPrivate) { #if defined(QT_NO_STYLE_WINDOWSVISTA) Q_UNUSED(wizardPrivate); #endif const bool macStyle = (wstyle == QWizard::MacStyle); switch (which) { case QWizard::BackButton: return macStyle ? QWizard::tr("Go Back") : QWizard::tr("< &Back"); case QWizard::NextButton: if (macStyle) return QWizard::tr("Continue"); else return wizardPrivate->isVistaThemeEnabled() ? QWizard::tr("&Next") : QWizard::tr("&Next >"); case QWizard::CommitButton: return QWizard::tr("Commit"); case QWizard::FinishButton: return macStyle ? QWizard::tr("Done") : QWizard::tr("&Finish"); case QWizard::CancelButton: return QWizard::tr("Cancel"); case QWizard::HelpButton: return macStyle ? QWizard::tr("Help") : QWizard::tr("&Help"); default: return QString(); } } void QWizardPrivate::init() { Q_Q(QWizard); antiFlickerWidget = new QWizardAntiFlickerWidget(q, this); wizStyle = QWizard::WizardStyle(q->style()->styleHint(QStyle::SH_WizardStyle, 0, q)); if (wizStyle == QWizard::MacStyle) { opts = (QWizard::NoDefaultButton | QWizard::NoCancelButton); } else if (wizStyle == QWizard::ModernStyle) { opts = QWizard::HelpButtonOnRight; } #if !defined(QT_NO_STYLE_WINDOWSVISTA) vistaHelper = new QVistaHelper(q); #endif // create these buttons right away; create the other buttons as necessary ensureButton(QWizard::BackButton); ensureButton(QWizard::NextButton); ensureButton(QWizard::CommitButton); ensureButton(QWizard::FinishButton); pageFrame = new QFrame(antiFlickerWidget); pageFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); pageVBoxLayout = new QVBoxLayout(pageFrame); pageVBoxLayout->setSpacing(0); pageVBoxLayout->addSpacing(0); QSpacerItem *spacerItem = new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); pageVBoxLayout->addItem(spacerItem); buttonLayout = new QHBoxLayout; mainLayout = new QGridLayout(antiFlickerWidget); mainLayout->setSizeConstraint(QLayout::SetNoConstraint); updateButtonLayout(); for (int i = 0; i < NFallbackDefaultProperties; ++i) defaultPropertyTable.append(QWizardDefaultProperty(fallbackProperties[i].className, fallbackProperties[i].property, fallbackProperties[i].changedSignal)); } void QWizardPrivate::reset() { Q_Q(QWizard); if (current != -1) { q->currentPage()->hide(); cleanupPagesNotInHistory(); for (int i = history.count() - 1; i >= 0; --i) q->cleanupPage(history.at(i)); history.clear(); initialized.clear(); current = -1; emit q->currentIdChanged(-1); } } void QWizardPrivate::cleanupPagesNotInHistory() { Q_Q(QWizard); const QSet original = initialized; QSet::const_iterator i = original.constBegin(); QSet::const_iterator end = original.constEnd(); for (; i != end; ++i) { if (!history.contains(*i)) { q->cleanupPage(*i); initialized.remove(*i); } } } void QWizardPrivate::addField(const QWizardField &field) { Q_Q(QWizard); QWizardField myField = field; myField.resolve(defaultPropertyTable); if (fieldIndexMap.contains(myField.name)) { qWarning("QWizardPage::addField: Duplicate field '%s'", qPrintable(myField.name)); return; } fieldIndexMap.insert(myField.name, fields.count()); fields += myField; if (myField.mandatory && !myField.changedSignal.isEmpty()) QObject::connect(myField.object, myField.changedSignal, myField.page, SLOT(_q_maybeEmitCompleteChanged())); QObject::connect( myField.object, SIGNAL(destroyed(QObject*)), q, SLOT(_q_handleFieldObjectDestroyed(QObject*))); } void QWizardPrivate::removeFieldAt(int index) { Q_Q(QWizard); const QWizardField &field = fields.at(index); fieldIndexMap.remove(field.name); if (field.mandatory && !field.changedSignal.isEmpty()) QObject::disconnect(field.object, field.changedSignal, field.page, SLOT(_q_maybeEmitCompleteChanged())); QObject::disconnect( field.object, SIGNAL(destroyed(QObject*)), q, SLOT(_q_handleFieldObjectDestroyed(QObject*))); fields.remove(index); } void QWizardPrivate::switchToPage(int newId, Direction direction) { Q_Q(QWizard); disableUpdates(); int oldId = current; if (QWizardPage *oldPage = q->currentPage()) { oldPage->hide(); if (direction == Backward) { if (!(opts & QWizard::IndependentPages)) { q->cleanupPage(oldId); initialized.remove(oldId); } Q_ASSERT(history.last() == oldId); history.removeLast(); Q_ASSERT(history.last() == newId); } } current = newId; QWizardPage *newPage = q->currentPage(); if (newPage) { if (direction == Forward) { if (!initialized.contains(current)) { initialized.insert(current); q->initializePage(current); } history.append(current); } newPage->show(); } canContinue = (q->nextId() != -1); canFinish = (newPage && newPage->isFinalPage()); _q_updateButtonStates(); updateButtonTexts(); const QWizard::WizardButton nextOrCommit = newPage && newPage->isCommitPage() ? QWizard::CommitButton : QWizard::NextButton; QAbstractButton *nextOrFinishButton = btns[canContinue ? nextOrCommit : QWizard::FinishButton]; QWidget *candidate = 0; /* If there is no default button and the Next or Finish button is enabled, give focus directly to it as a convenience to the user. This is the normal case on Mac OS X. Otherwise, give the focus to the new page's first child that can handle it. If there is no such child, give the focus to Next or Finish. */ if ((opts & QWizard::NoDefaultButton) && nextOrFinishButton->isEnabled()) { candidate = nextOrFinishButton; } else if (newPage) { candidate = iWantTheFocus(newPage); } if (!candidate) candidate = nextOrFinishButton; candidate->setFocus(); if (wizStyle == QWizard::MacStyle) q->updateGeometry(); enableUpdates(); updateLayout(); emit q->currentIdChanged(current); } // keep in sync with QWizard::WizardButton static const char * const buttonSlots[QWizard::NStandardButtons] = { SLOT(back()), SLOT(next()), SLOT(next()), SLOT(accept()), SLOT(reject()), SIGNAL(helpRequested()) }; QWizardLayoutInfo QWizardPrivate::layoutInfoForCurrentPage() { Q_Q(QWizard); QStyle *style = q->style(); QWizardLayoutInfo info; const int layoutHorizontalSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); info.topLevelMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, q); info.topLevelMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, q); info.topLevelMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, q); info.topLevelMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, q); info.childMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, titleLabel); info.childMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, titleLabel); info.childMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, titleLabel); info.childMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, titleLabel); info.hspacing = (layoutHorizontalSpacing == -1) ? style->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal) : layoutHorizontalSpacing; info.vspacing = style->pixelMetric(QStyle::PM_LayoutVerticalSpacing); info.buttonSpacing = (layoutHorizontalSpacing == -1) ? style->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal) : layoutHorizontalSpacing; if (wizStyle == QWizard::MacStyle) info.buttonSpacing = 12; info.wizStyle = wizStyle; if (info.wizStyle == QWizard::AeroStyle #if !defined(QT_NO_STYLE_WINDOWSVISTA) && (QVistaHelper::vistaState() == QVistaHelper::Classic || vistaDisabled()) #endif ) info.wizStyle = QWizard::ModernStyle; QString titleText; QString subTitleText; QPixmap backgroundPixmap; QPixmap watermarkPixmap; if (QWizardPage *page = q->currentPage()) { titleText = page->title(); subTitleText = page->subTitle(); backgroundPixmap = page->pixmap(QWizard::BackgroundPixmap); watermarkPixmap = page->pixmap(QWizard::WatermarkPixmap); } info.header = (info.wizStyle == QWizard::ClassicStyle || info.wizStyle == QWizard::ModernStyle) && !(opts & QWizard::IgnoreSubTitles) && !subTitleText.isEmpty(); info.sideWidget = sideWidget; info.watermark = (info.wizStyle != QWizard::MacStyle) && (info.wizStyle != QWizard::AeroStyle) && !watermarkPixmap.isNull(); info.title = !info.header && !titleText.isEmpty(); info.subTitle = !(opts & QWizard::IgnoreSubTitles) && !info.header && !subTitleText.isEmpty(); info.extension = (info.watermark || info.sideWidget) && (opts & QWizard::ExtendedWatermarkPixmap); return info; } void QWizardPrivate::recreateLayout(const QWizardLayoutInfo &info) { Q_Q(QWizard); /* Start by undoing the main layout. */ for (int i = mainLayout->count() - 1; i >= 0; --i) { QLayoutItem *item = mainLayout->takeAt(i); if (item->layout()) { item->layout()->setParent(0); } else { delete item; } } for (int i = mainLayout->columnCount() - 1; i >= 0; --i) mainLayout->setColumnMinimumWidth(i, 0); for (int i = mainLayout->rowCount() - 1; i >= 0; --i) mainLayout->setRowMinimumHeight(i, 0); /* Now, recreate it. */ bool mac = (info.wizStyle == QWizard::MacStyle); bool classic = (info.wizStyle == QWizard::ClassicStyle); bool modern = (info.wizStyle == QWizard::ModernStyle); bool aero = (info.wizStyle == QWizard::AeroStyle); int deltaMarginLeft = info.topLevelMarginLeft - info.childMarginLeft; int deltaMarginRight = info.topLevelMarginRight - info.childMarginRight; int deltaMarginTop = info.topLevelMarginTop - info.childMarginTop; int deltaMarginBottom = info.topLevelMarginBottom - info.childMarginBottom; int deltaVSpacing = info.topLevelMarginBottom - info.vspacing; int row = 0; int numColumns; if (mac) { numColumns = 3; } else if (info.watermark || info.sideWidget) { numColumns = 2; } else { numColumns = 1; } int pageColumn = qMin(1, numColumns - 1); if (mac) { mainLayout->setMargin(0); mainLayout->setSpacing(0); buttonLayout->setContentsMargins(MacLayoutLeftMargin, MacButtonTopMargin, MacLayoutRightMargin, MacLayoutBottomMargin); pageVBoxLayout->setMargin(7); } else { if (modern) { mainLayout->setMargin(0); mainLayout->setSpacing(0); pageVBoxLayout->setContentsMargins(deltaMarginLeft, deltaMarginTop, deltaMarginRight, deltaMarginBottom); buttonLayout->setContentsMargins(info.topLevelMarginLeft, info.topLevelMarginTop, info.topLevelMarginRight, info.topLevelMarginBottom); } else { mainLayout->setContentsMargins(info.topLevelMarginLeft, info.topLevelMarginTop, info.topLevelMarginRight, info.topLevelMarginBottom); mainLayout->setHorizontalSpacing(info.hspacing); mainLayout->setVerticalSpacing(info.vspacing); pageVBoxLayout->setContentsMargins(0, 0, 0, 0); buttonLayout->setContentsMargins(0, 0, 0, 0); } } buttonLayout->setSpacing(info.buttonSpacing); if (info.header) { if (!headerWidget) headerWidget = new QWizardHeader(antiFlickerWidget); headerWidget->setAutoFillBackground(modern); mainLayout->addWidget(headerWidget, row++, 0, 1, numColumns); } if (headerWidget) headerWidget->setVisible(info.header); int watermarkStartRow = row; if (mac) mainLayout->setRowMinimumHeight(row++, 10); if (info.title) { if (!titleLabel) { titleLabel = new QLabel(antiFlickerWidget); titleLabel->setBackgroundRole(QPalette::Base); titleLabel->setWordWrap(true); } QFont titleFont = q->font(); titleFont.setPointSize(titleFont.pointSize() + (mac ? 3 : 4)); titleFont.setBold(true); titleLabel->setPalette(QPalette()); if (aero) { // ### hardcoded for now: titleFont = QFont(QLatin1String("Segoe UI"), 12); QPalette pal(titleLabel->palette()); pal.setColor(QPalette::Text, "#003399"); titleLabel->setPalette(pal); } titleLabel->setFont(titleFont); const int aeroTitleIndent = 25; // ### hardcoded for now - should be calculated somehow if (aero) titleLabel->setIndent(aeroTitleIndent); else if (mac) titleLabel->setIndent(2); else if (classic) titleLabel->setIndent(info.childMarginLeft); else titleLabel->setIndent(info.topLevelMarginLeft); if (modern) { if (!placeholderWidget1) { placeholderWidget1 = new QWidget(antiFlickerWidget); placeholderWidget1->setBackgroundRole(QPalette::Base); } placeholderWidget1->setFixedHeight(info.topLevelMarginLeft + 2); mainLayout->addWidget(placeholderWidget1, row++, pageColumn); } mainLayout->addWidget(titleLabel, row++, pageColumn); if (modern) { if (!placeholderWidget2) { placeholderWidget2 = new QWidget(antiFlickerWidget); placeholderWidget2->setBackgroundRole(QPalette::Base); } placeholderWidget2->setFixedHeight(5); mainLayout->addWidget(placeholderWidget2, row++, pageColumn); } if (mac) mainLayout->setRowMinimumHeight(row++, 7); } if (placeholderWidget1) placeholderWidget1->setVisible(info.title && modern); if (placeholderWidget2) placeholderWidget2->setVisible(info.title && modern); if (info.subTitle) { if (!subTitleLabel) { subTitleLabel = new QLabel(pageFrame); subTitleLabel->setWordWrap(true); subTitleLabel->setContentsMargins(info.childMarginLeft , 0, info.childMarginRight , 0); pageVBoxLayout->insertWidget(1, subTitleLabel); } } // ### try to replace with margin. changeSpacerSize(pageVBoxLayout, 0, 0, info.subTitle ? info.childMarginLeft : 0); int hMargin = mac ? 1 : 0; int vMargin = hMargin; pageFrame->setFrameStyle(mac ? (QFrame::Box | QFrame::Raised) : QFrame::NoFrame); pageFrame->setLineWidth(0); pageFrame->setMidLineWidth(hMargin); if (info.header) { if (modern) { hMargin = info.topLevelMarginLeft; vMargin = deltaMarginBottom; } else if (classic) { hMargin = deltaMarginLeft + ClassicHMargin; vMargin = 0; } } if (aero) { int leftMargin = 18; // ### hardcoded for now - should be calculated somehow int topMargin = vMargin; int rightMargin = hMargin; // ### for now int bottomMargin = vMargin; pageFrame->setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin); } else { pageFrame->setContentsMargins(hMargin, vMargin, hMargin, vMargin); } if ((info.watermark || info.sideWidget) && !watermarkLabel) { watermarkLabel = new QWatermarkLabel(antiFlickerWidget, sideWidget); watermarkLabel->setBackgroundRole(QPalette::Base); watermarkLabel->setMinimumHeight(1); watermarkLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); watermarkLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); } //bool wasSemiTransparent = pageFrame->testAttribute(Qt::WA_SetPalette); const bool wasSemiTransparent = pageFrame->palette().brush(QPalette::Window).color().alpha() < 255 || pageFrame->palette().brush(QPalette::Base).color().alpha() < 255; if (mac) { if (!wasSemiTransparent) { QPalette pal = pageFrame->palette(); pal.setBrush(QPalette::Window, QColor(255, 255, 255, 153)); // ### The next line is required to ensure visual semitransparency when // ### switching from ModernStyle to MacStyle. See TAG1 below. pal.setBrush(QPalette::Base, QColor(255, 255, 255, 153)); pageFrame->setPalette(pal); pageFrame->setAutoFillBackground(true); antiFlickerWidget->setAutoFillBackground(false); } } else { if (wasSemiTransparent) pageFrame->setPalette(QPalette()); bool baseBackground = (modern && !info.header); // ### TAG1 pageFrame->setBackgroundRole(baseBackground ? QPalette::Base : QPalette::Window); if (titleLabel) titleLabel->setAutoFillBackground(baseBackground); pageFrame->setAutoFillBackground(baseBackground); if (watermarkLabel) watermarkLabel->setAutoFillBackground(baseBackground); if (placeholderWidget1) placeholderWidget1->setAutoFillBackground(baseBackground); if (placeholderWidget2) placeholderWidget2->setAutoFillBackground(baseBackground); if (aero) { QPalette pal = pageFrame->palette(); pal.setBrush(QPalette::Window, QColor(255, 255, 255)); pageFrame->setPalette(pal); pageFrame->setAutoFillBackground(true); pal = antiFlickerWidget->palette(); pal.setBrush(QPalette::Window, QColor(255, 255, 255)); antiFlickerWidget->setPalette(pal); antiFlickerWidget->setAutoFillBackground(true); } } mainLayout->addWidget(pageFrame, row++, pageColumn); int watermarkEndRow = row; if (classic) mainLayout->setRowMinimumHeight(row++, deltaVSpacing); if (aero) { buttonLayout->setContentsMargins(9, 9, 9, 9); mainLayout->setContentsMargins(0, 11, 0, 0); } int buttonStartColumn = info.extension ? 1 : 0; int buttonNumColumns = info.extension ? 1 : numColumns; if (classic || modern) { if (!bottomRuler) bottomRuler = new QWizardRuler(antiFlickerWidget); mainLayout->addWidget(bottomRuler, row++, buttonStartColumn, 1, buttonNumColumns); } if (classic) mainLayout->setRowMinimumHeight(row++, deltaVSpacing); mainLayout->addLayout(buttonLayout, row++, buttonStartColumn, 1, buttonNumColumns); if (info.watermark || info.sideWidget) { if (info.extension) watermarkEndRow = row; mainLayout->addWidget(watermarkLabel, watermarkStartRow, 0, watermarkEndRow - watermarkStartRow, 1); } mainLayout->setColumnMinimumWidth(0, mac && !info.watermark ? 181 : 0); if (mac) mainLayout->setColumnMinimumWidth(2, 21); if (headerWidget) headerWidget->setVisible(info.header); if (titleLabel) titleLabel->setVisible(info.title); if (subTitleLabel) subTitleLabel->setVisible(info.subTitle); if (bottomRuler) bottomRuler->setVisible(classic || modern); if (watermarkLabel) watermarkLabel->setVisible(info.watermark || info.sideWidget); layoutInfo = info; } void QWizardPrivate::updateLayout() { Q_Q(QWizard); disableUpdates(); QWizardLayoutInfo info = layoutInfoForCurrentPage(); if (layoutInfo != info) recreateLayout(info); QWizardPage *page = q->currentPage(); // If the page can expand vertically, let it stretch "infinitely" more // than the QSpacerItem at the bottom. Otherwise, let the QSpacerItem // stretch "infinitely" more than the page. Change the bottom item's // policy accordingly. The case that the page has no layout is basically // for Designer, only. if (page) { bool expandPage = !page->layout(); if (!expandPage) { const QLayoutItem *pageItem = pageVBoxLayout->itemAt(pageVBoxLayout->indexOf(page)); expandPage = pageItem->expandingDirections() & Qt::Vertical; } QSpacerItem *bottomSpacer = pageVBoxLayout->itemAt(pageVBoxLayout->count() - 1)->spacerItem(); Q_ASSERT(bottomSpacer); bottomSpacer->changeSize(0, 0, QSizePolicy::Ignored, expandPage ? QSizePolicy::Ignored : QSizePolicy::MinimumExpanding); pageVBoxLayout->invalidate(); } if (info.header) { Q_ASSERT(page); headerWidget->setup(info, page->title(), page->subTitle(), page->pixmap(QWizard::LogoPixmap), page->pixmap(QWizard::BannerPixmap), titleFmt, subTitleFmt); } if (info.watermark || info.sideWidget) { QPixmap pix; if (info.watermark) { if (page) pix = page->pixmap(QWizard::WatermarkPixmap); else pix = q->pixmap(QWizard::WatermarkPixmap); } watermarkLabel->setPixmap(pix); // in case there is no watermark and we show the side widget we need to clear the watermark } if (info.title) { Q_ASSERT(page); titleLabel->setTextFormat(titleFmt); titleLabel->setText(page->title()); } if (info.subTitle) { Q_ASSERT(page); subTitleLabel->setTextFormat(subTitleFmt); subTitleLabel->setText(page->subTitle()); } enableUpdates(); updateMinMaxSizes(info); } void QWizardPrivate::updateMinMaxSizes(const QWizardLayoutInfo &info) { Q_Q(QWizard); int extraHeight = 0; #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (isVistaThemeEnabled()) extraHeight = vistaHelper->titleBarSize() + vistaHelper->topOffset(); #endif QSize minimumSize = mainLayout->totalMinimumSize() + QSize(0, extraHeight); QSize maximumSize = mainLayout->totalMaximumSize(); if (info.header && headerWidget->maximumWidth() != QWIDGETSIZE_MAX) { minimumSize.setWidth(headerWidget->maximumWidth()); maximumSize.setWidth(headerWidget->maximumWidth()); } if (info.watermark && !info.sideWidget) { minimumSize.setHeight(mainLayout->totalSizeHint().height()); maximumSize.setHeight(mainLayout->totalSizeHint().height()); } if (q->minimumWidth() == minimumWidth) { minimumWidth = minimumSize.width(); q->setMinimumWidth(minimumWidth); } if (q->minimumHeight() == minimumHeight) { minimumHeight = minimumSize.height(); q->setMinimumHeight(minimumHeight); } if (q->maximumWidth() == maximumWidth) { maximumWidth = maximumSize.width(); q->setMaximumWidth(maximumWidth); } if (q->maximumHeight() == maximumHeight) { maximumHeight = maximumSize.height(); q->setMaximumHeight(maximumHeight); } } void QWizardPrivate::updateCurrentPage() { Q_Q(QWizard); if (q->currentPage()) { canContinue = (q->nextId() != -1); canFinish = q->currentPage()->isFinalPage(); } else { canContinue = false; canFinish = false; } _q_updateButtonStates(); updateButtonTexts(); } static QString object_name_for_button(QWizard::WizardButton which) { switch (which) { case QWizard::CommitButton: return QLatin1String("qt_wizard_") + QLatin1String("commit"); case QWizard::FinishButton: return QLatin1String("qt_wizard_") + QLatin1String("finish"); case QWizard::CancelButton: return QLatin1String("qt_wizard_") + QLatin1String("cancel"); case QWizard::BackButton: case QWizard::NextButton: case QWizard::HelpButton: case QWizard::CustomButton1: case QWizard::CustomButton2: case QWizard::CustomButton3: // Make navigation buttons detectable as passive interactor in designer return QLatin1String("__qt__passive_wizardbutton") + QString::number(which); case QWizard::Stretch: case QWizard::NoButton: //case QWizard::NStandardButtons: //case QWizard::NButtons: ; } Q_UNREACHABLE(); return QString(); } bool QWizardPrivate::ensureButton(QWizard::WizardButton which) const { Q_Q(const QWizard); if (uint(which) >= QWizard::NButtons) return false; if (!btns[which]) { QPushButton *pushButton = new QPushButton(antiFlickerWidget); QStyle *style = q->style(); if (style != QApplication::style()) // Propagate style pushButton->setStyle(style); pushButton->setObjectName(object_name_for_button(which)); #ifdef Q_OS_MACX pushButton->setAutoDefault(false); #endif pushButton->hide(); #ifdef Q_CC_HPACC const_cast(this)->btns[which] = pushButton; #else btns[which] = pushButton; #endif if (which < QWizard::NStandardButtons) pushButton->setText(buttonDefaultText(wizStyle, which, this)); connectButton(which); } return true; } void QWizardPrivate::connectButton(QWizard::WizardButton which) const { Q_Q(const QWizard); if (which < QWizard::NStandardButtons) { QObject::connect(btns[which], SIGNAL(clicked()), q, buttonSlots[which]); } else { QObject::connect(btns[which], SIGNAL(clicked()), q, SLOT(_q_emitCustomButtonClicked())); } } void QWizardPrivate::updateButtonTexts() { Q_Q(QWizard); for (int i = 0; i < QWizard::NButtons; ++i) { if (btns[i]) { if (q->currentPage() && (q->currentPage()->d_func()->buttonCustomTexts.contains(i))) btns[i]->setText(q->currentPage()->d_func()->buttonCustomTexts.value(i)); else if (buttonCustomTexts.contains(i)) btns[i]->setText(buttonCustomTexts.value(i)); else if (i < QWizard::NStandardButtons) btns[i]->setText(buttonDefaultText(wizStyle, i, this)); } } } void QWizardPrivate::updateButtonLayout() { if (buttonsHaveCustomLayout) { QVarLengthArray array(buttonsCustomLayout.count()); for (int i = 0; i < buttonsCustomLayout.count(); ++i) array[i] = buttonsCustomLayout.at(i); setButtonLayout(array.constData(), array.count()); } else { // Positions: // Help Stretch Custom1 Custom2 Custom3 Cancel Back Next Commit Finish Cancel Help const int ArraySize = 12; QWizard::WizardButton array[ArraySize]; memset(array, -1, sizeof(array)); Q_ASSERT(array[0] == QWizard::NoButton); if (opts & QWizard::HaveHelpButton) { int i = (opts & QWizard::HelpButtonOnRight) ? 11 : 0; array[i] = QWizard::HelpButton; } array[1] = QWizard::Stretch; if (opts & QWizard::HaveCustomButton1) array[2] = QWizard::CustomButton1; if (opts & QWizard::HaveCustomButton2) array[3] = QWizard::CustomButton2; if (opts & QWizard::HaveCustomButton3) array[4] = QWizard::CustomButton3; if (!(opts & QWizard::NoCancelButton)) { int i = (opts & QWizard::CancelButtonOnLeft) ? 5 : 10; array[i] = QWizard::CancelButton; } array[6] = QWizard::BackButton; array[7] = QWizard::NextButton; array[8] = QWizard::CommitButton; array[9] = QWizard::FinishButton; setButtonLayout(array, ArraySize); } } void QWizardPrivate::setButtonLayout(const QWizard::WizardButton *array, int size) { QWidget *prev = pageFrame; for (int i = buttonLayout->count() - 1; i >= 0; --i) { QLayoutItem *item = buttonLayout->takeAt(i); if (QWidget *widget = item->widget()) widget->hide(); delete item; } for (int i = 0; i < size; ++i) { QWizard::WizardButton which = array[i]; if (which == QWizard::Stretch) { buttonLayout->addStretch(1); } else if (which != QWizard::NoButton) { ensureButton(which); buttonLayout->addWidget(btns[which]); // Back, Next, Commit, and Finish are handled in _q_updateButtonStates() if (which != QWizard::BackButton && which != QWizard::NextButton && which != QWizard::CommitButton && which != QWizard::FinishButton) btns[which]->show(); if (prev) QWidget::setTabOrder(prev, btns[which]); prev = btns[which]; } } _q_updateButtonStates(); } bool QWizardPrivate::buttonLayoutContains(QWizard::WizardButton which) { return !buttonsHaveCustomLayout || buttonsCustomLayout.contains(which); } void QWizardPrivate::updatePixmap(QWizard::WizardPixmap which) { Q_Q(QWizard); if (which == QWizard::BackgroundPixmap) { if (wizStyle == QWizard::MacStyle) { q->update(); q->updateGeometry(); } } else { updateLayout(); } } #if !defined(QT_NO_STYLE_WINDOWSVISTA) bool QWizardPrivate::vistaDisabled() const { Q_Q(const QWizard); const QVariant v = q->property("_q_wizard_vista_off"); return v.isValid() && v.toBool(); } bool QWizardPrivate::isVistaThemeEnabled(QVistaHelper::VistaState state) const { return wizStyle == QWizard::AeroStyle && QVistaHelper::vistaState() == state && !vistaDisabled(); } bool QWizardPrivate::handleAeroStyleChange() { Q_Q(QWizard); if (inHandleAeroStyleChange) return false; // prevent recursion // For top-level wizards, we need the platform window handle for the // DWM changes. Delay aero initialization to the show event handling if // it does not exist. If we are a child, skip DWM and just make room by // moving the antiFlickerWidget. const bool isWindow = q->isWindow(); if (isWindow && (!q->windowHandle() || !q->windowHandle()->handle())) return false; inHandleAeroStyleChange = true; vistaHelper->disconnectBackButton(); q->removeEventFilter(vistaHelper); bool vistaMargins = false; if (isVistaThemeEnabled()) { if (isVistaThemeEnabled(QVistaHelper::VistaAero)) { if (isWindow) { vistaHelper->setDWMTitleBar(QVistaHelper::ExtendedTitleBar); q->installEventFilter(vistaHelper); } q->setMouseTracking(true); antiFlickerWidget->move(0, vistaHelper->titleBarSize() + vistaHelper->topOffset()); vistaHelper->backButton()->move( 0, vistaHelper->topOffset() // ### should ideally work without the '+ 1' - qMin(vistaHelper->topOffset(), vistaHelper->topPadding() + 1)); vistaMargins = true; vistaHelper->backButton()->show(); } else { if (isWindow) vistaHelper->setDWMTitleBar(QVistaHelper::NormalTitleBar); q->setMouseTracking(true); antiFlickerWidget->move(0, vistaHelper->topOffset()); vistaHelper->backButton()->move(0, -1); // ### should ideally work with (0, 0) } if (isWindow) vistaHelper->setTitleBarIconAndCaptionVisible(false); QObject::connect( vistaHelper->backButton(), SIGNAL(clicked()), q, buttonSlots[QWizard::BackButton]); vistaHelper->backButton()->show(); } else { q->setMouseTracking(true); // ### original value possibly different #ifndef QT_NO_CURSOR q->unsetCursor(); // ### ditto #endif antiFlickerWidget->move(0, 0); vistaHelper->hideBackButton(); if (isWindow) vistaHelper->setTitleBarIconAndCaptionVisible(true); } _q_updateButtonStates(); vistaHelper->updateCustomMargins(vistaMargins); inHandleAeroStyleChange = false; return true; } #endif bool QWizardPrivate::isVistaThemeEnabled() const { #if !defined(QT_NO_STYLE_WINDOWSVISTA) return isVistaThemeEnabled(QVistaHelper::VistaAero) || isVistaThemeEnabled(QVistaHelper::VistaBasic); #else return false; #endif } void QWizardPrivate::disableUpdates() { Q_Q(QWizard); if (disableUpdatesCount++ == 0) { q->setUpdatesEnabled(false); antiFlickerWidget->hide(); } } void QWizardPrivate::enableUpdates() { Q_Q(QWizard); if (--disableUpdatesCount == 0) { antiFlickerWidget->show(); q->setUpdatesEnabled(true); } } void QWizardPrivate::_q_emitCustomButtonClicked() { Q_Q(QWizard); QObject *button = q->sender(); for (int i = QWizard::NStandardButtons; i < QWizard::NButtons; ++i) { if (btns[i] == button) { emit q->customButtonClicked(QWizard::WizardButton(i)); break; } } } void QWizardPrivate::_q_updateButtonStates() { Q_Q(QWizard); disableUpdates(); const QWizardPage *page = q->currentPage(); bool complete = page && page->isComplete(); btn.back->setEnabled(history.count() > 1 && !q->page(history.at(history.count() - 2))->isCommitPage() && (!canFinish || !(opts & QWizard::DisabledBackButtonOnLastPage))); btn.next->setEnabled(canContinue && complete); btn.commit->setEnabled(canContinue && complete); btn.finish->setEnabled(canFinish && complete); const bool backButtonVisible = buttonLayoutContains(QWizard::BackButton) && (history.count() > 1 || !(opts & QWizard::NoBackButtonOnStartPage)) && (canContinue || !(opts & QWizard::NoBackButtonOnLastPage)); bool commitPage = page && page->isCommitPage(); btn.back->setVisible(backButtonVisible); btn.next->setVisible(buttonLayoutContains(QWizard::NextButton) && !commitPage && (canContinue || (opts & QWizard::HaveNextButtonOnLastPage))); btn.commit->setVisible(buttonLayoutContains(QWizard::CommitButton) && commitPage && canContinue); btn.finish->setVisible(buttonLayoutContains(QWizard::FinishButton) && (canFinish || (opts & QWizard::HaveFinishButtonOnEarlyPages))); bool useDefault = !(opts & QWizard::NoDefaultButton); if (QPushButton *nextPush = qobject_cast(btn.next)) nextPush->setDefault(canContinue && useDefault && !commitPage); if (QPushButton *commitPush = qobject_cast(btn.commit)) commitPush->setDefault(canContinue && useDefault && commitPage); if (QPushButton *finishPush = qobject_cast(btn.finish)) finishPush->setDefault(!canContinue && useDefault); #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (isVistaThemeEnabled()) { vistaHelper->backButton()->setEnabled(btn.back->isEnabled()); vistaHelper->backButton()->setVisible(backButtonVisible); btn.back->setVisible(false); } #endif enableUpdates(); } void QWizardPrivate::_q_handleFieldObjectDestroyed(QObject *object) { int destroyed_index = -1; QVector::iterator it = fields.begin(); while (it != fields.end()) { const QWizardField &field = *it; if (field.object == object) { destroyed_index = fieldIndexMap.value(field.name, -1); fieldIndexMap.remove(field.name); it = fields.erase(it); } else { ++it; } } if (destroyed_index != -1) { QMap::iterator it2 = fieldIndexMap.begin(); while (it2 != fieldIndexMap.end()) { int index = it2.value(); if (index > destroyed_index) { QString field_name = it2.key(); fieldIndexMap.insert(field_name, index-1); } ++it2; } } } void QWizardPrivate::setStyle(QStyle *style) { for (int i = 0; i < QWizard::NButtons; i++) if (btns[i]) btns[i]->setStyle(style); const PageMap::const_iterator pcend = pageMap.constEnd(); for (PageMap::const_iterator it = pageMap.constBegin(); it != pcend; ++it) it.value()->setStyle(style); } #ifdef Q_OS_MACX QPixmap QWizardPrivate::findDefaultBackgroundPixmap() { QGuiApplication *app = qobject_cast(QCoreApplication::instance()); if (!app) return QPixmap(); QPlatformNativeInterface *platformNativeInterface = app->platformNativeInterface(); int at = platformNativeInterface->metaObject()->indexOfMethod("defaultBackgroundPixmapForQWizard()"); if (at == -1) return QPixmap(); QMetaMethod defaultBackgroundPixmapForQWizard = platformNativeInterface->metaObject()->method(at); QPixmap result; if (!defaultBackgroundPixmapForQWizard.invoke(platformNativeInterface, Q_RETURN_ARG(QPixmap, result))) return QPixmap(); return result; } #endif #if !defined(QT_NO_STYLE_WINDOWSVISTA) void QWizardAntiFlickerWidget::paintEvent(QPaintEvent *) { if (wizardPrivate->isVistaThemeEnabled()) { int leftMargin, topMargin, rightMargin, bottomMargin; wizardPrivate->buttonLayout->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin); const int buttonLayoutTop = wizardPrivate->buttonLayout->contentsRect().top() - topMargin; QPainter painter(this); const QBrush brush(QColor(240, 240, 240)); // ### hardcoded for now painter.fillRect(0, buttonLayoutTop, width(), height() - buttonLayoutTop, brush); painter.setPen(QPen(QBrush(QColor(223, 223, 223)), 0)); // ### hardcoded for now painter.drawLine(0, buttonLayoutTop, width(), buttonLayoutTop); if (wizardPrivate->isVistaThemeEnabled(QVistaHelper::VistaBasic)) { if (window()->isActiveWindow()) painter.setPen(QPen(QBrush(QColor(169, 191, 214)), 0)); // ### hardcoded for now else painter.setPen(QPen(QBrush(QColor(182, 193, 204)), 0)); // ### hardcoded for now painter.drawLine(0, 0, width(), 0); } } } #endif /*! \class QWizard \since 4.3 \brief The QWizard class provides a framework for wizards. \inmodule QtWidgets A wizard (also called an assistant on Mac OS X) is a special type of input dialog that consists of a sequence of pages. A wizard's purpose is to guide the user through a process step by step. Wizards are useful for complex or infrequent tasks that users may find difficult to learn. QWizard inherits QDialog and represents a wizard. Each page is a QWizardPage (a QWidget subclass). To create your own wizards, you can use these classes directly, or you can subclass them for more control. Topics: \tableofcontents \section1 A Trivial Example The following example illustrates how to create wizard pages and add them to a wizard. For more advanced examples, see \l{dialogs/classwizard}{Class Wizard} and \l{dialogs/licensewizard}{License Wizard}. \snippet dialogs/trivialwizard/trivialwizard.cpp 1 \snippet dialogs/trivialwizard/trivialwizard.cpp 3 \dots \snippet dialogs/trivialwizard/trivialwizard.cpp 4 \codeline \snippet dialogs/trivialwizard/trivialwizard.cpp 5 \snippet dialogs/trivialwizard/trivialwizard.cpp 7 \dots \snippet dialogs/trivialwizard/trivialwizard.cpp 8 \codeline \snippet dialogs/trivialwizard/trivialwizard.cpp 10 \section1 Wizard Look and Feel QWizard supports four wizard looks: \list \li ClassicStyle \li ModernStyle \li MacStyle \li AeroStyle \endlist You can explicitly set the look to use using setWizardStyle() (e.g., if you want the same look on all platforms). \table \header \li ClassicStyle \li ModernStyle \li MacStyle \li AeroStyle \row \li \inlineimage qtwizard-classic1.png \li \inlineimage qtwizard-modern1.png \li \inlineimage qtwizard-mac1.png \li \inlineimage qtwizard-aero1.png \row \li \inlineimage qtwizard-classic2.png \li \inlineimage qtwizard-modern2.png \li \inlineimage qtwizard-mac2.png \li \inlineimage qtwizard-aero2.png \endtable Note: AeroStyle has effect only on a Windows Vista system with alpha compositing enabled. ModernStyle is used as a fallback when this condition is not met. In addition to the wizard style, there are several options that control the look and feel of the wizard. These can be set using setOption() or setOptions(). For example, HaveHelpButton makes QWizard show a \uicontrol Help button along with the other wizard buttons. You can even change the order of the wizard buttons to any arbitrary order using setButtonLayout(), and you can add up to three custom buttons (e.g., a \uicontrol Print button) to the button row. This is achieved by calling setButton() or setButtonText() with CustomButton1, CustomButton2, or CustomButton3 to set up the button, and by enabling the HaveCustomButton1, HaveCustomButton2, or HaveCustomButton3 options. Whenever the user clicks a custom button, customButtonClicked() is emitted. For example: \snippet dialogs/licensewizard/licensewizard.cpp 29 \section1 Elements of a Wizard Page Wizards consist of a sequence of \l{QWizardPage}s. At any time, only one page is shown. A page has the following attributes: \list \li A \l{QWizardPage::}{title}. \li A \l{QWizardPage::}{subTitle}. \li A set of pixmaps, which may or may not be honored, depending on the wizard's style: \list \li WatermarkPixmap (used by ClassicStyle and ModernStyle) \li BannerPixmap (used by ModernStyle) \li LogoPixmap (used by ClassicStyle and ModernStyle) \li BackgroundPixmap (used by MacStyle) \endlist \endlist The diagram belows shows how QWizard renders these attributes, assuming they are all present and ModernStyle is used: \image qtwizard-nonmacpage.png When a \l{QWizardPage::}{subTitle} is set, QWizard displays it in a header, in which case it also uses the BannerPixmap and the LogoPixmap to decorate the header. The WatermarkPixmap is displayed on the left side, below the header. At the bottom, there is a row of buttons allowing the user to navigate through the pages. The page itself (the \l{QWizardPage} widget) occupies the area between the header, the watermark, and the button row. Typically, the page is a QWizardPage on which a QGridLayout is installed, with standard child widgets (\l{QLabel}s, \l{QLineEdit}s, etc.). If the wizard's style is MacStyle, the page looks radically different: \image qtwizard-macpage.png The watermark, banner, and logo pixmaps are ignored by the MacStyle. If the BackgroundPixmap is set, it is used as the background for the wizard; otherwise, a default "assistant" image is used. The title and subtitle are set by calling QWizardPage::setTitle() and QWizardPage::setSubTitle() on the individual pages. They may be plain text or HTML (see titleFormat and subTitleFormat). The pixmaps can be set globally for the entire wizard using setPixmap(), or on a per-page basis using QWizardPage::setPixmap(). \target field mechanism \section1 Registering and Using Fields In many wizards, the contents of a page may affect the default values of the fields of a later page. To make it easy to communicate between pages, QWizard supports a "field" mechanism that allows you to register a field (e.g., a QLineEdit) on a page and to access its value from any page. It is also possible to specify mandatory fields (i.e., fields that must be filled before the user can advance to the next page). To register a field, call QWizardPage::registerField() field. For example: \snippet dialogs/classwizard/classwizard.cpp 8 \dots \snippet dialogs/classwizard/classwizard.cpp 10 \snippet dialogs/classwizard/classwizard.cpp 11 \dots \snippet dialogs/classwizard/classwizard.cpp 13 The above code registers three fields, \c className, \c baseClass, and \c qobjectMacro, which are associated with three child widgets. The asterisk (\c *) next to \c className denotes a mandatory field. \target initialize page The fields of any page are accessible from any other page. For example: \snippet dialogs/classwizard/classwizard.cpp 17 Here, we call QWizardPage::field() to access the contents of the \c className field (which was defined in the \c ClassInfoPage) and use it to initialize the \c OutputFilePage. The field's contents is returned as a QVariant. When we create a field using QWizardPage::registerField(), we pass a unique field name and a widget. We can also provide a Qt property name and a "changed" signal (a signal that is emitted when the property changes) as third and fourth arguments; however, this is not necessary for the most common Qt widgets, such as QLineEdit, QCheckBox, and QComboBox, because QWizard knows which properties to look for. \target mandatory fields If an asterisk (\c *) is appended to the name when the property is registered, the field is a \e{mandatory field}. When a page has mandatory fields, the \uicontrol Next and/or \uicontrol Finish buttons are enabled only when all mandatory fields are filled. To consider a field "filled", QWizard simply checks that the field's current value doesn't equal the original value (the value it had when initializePage() was called). For QLineEdit and QAbstractSpinBox subclasses, QWizard also checks that \l{QLineEdit::hasAcceptableInput()}{hasAcceptableInput()} returns true, to honor any validator or mask. QWizard's mandatory field mechanism is provided for convenience. A more powerful (but also more cumbersome) alternative is to reimplement QWizardPage::isComplete() and to emit the QWizardPage::completeChanged() signal whenever the page becomes complete or incomplete. The enabled/disabled state of the \uicontrol Next and/or \uicontrol Finish buttons is one way to perform validation on the user input. Another way is to reimplement validateCurrentPage() (or QWizardPage::validatePage()) to perform some last-minute validation (and show an error message if the user has entered incomplete or invalid information). If the function returns \c true, the next page is shown (or the wizard finishes); otherwise, the current page stays up. \section1 Creating Linear Wizards Most wizards have a linear structure, with page 1 followed by page 2 and so on until the last page. The \l{dialogs/classwizard}{Class Wizard} example is such a wizard. With QWizard, linear wizards are created by instantiating the \l{QWizardPage}s and inserting them using addPage(). By default, the pages are shown in the order in which they were added. For example: \snippet dialogs/classwizard/classwizard.cpp 0 \dots \snippet dialogs/classwizard/classwizard.cpp 2 When a page is about to be shown, QWizard calls initializePage() (which in turn calls QWizardPage::initializePage()) to fill the page with default values. By default, this function does nothing, but it can be reimplemented to initialize the page's contents based on other pages' fields (see the \l{initialize page}{example above}). If the user presses \uicontrol Back, cleanupPage() is called (which in turn calls QWizardPage::cleanupPage()). The default implementation resets the page's fields to their original values (the values they had before initializePage() was called). If you want the \uicontrol Back button to be non-destructive and keep the values entered by the user, simply enable the IndependentPages option. \section1 Creating Non-Linear Wizards Some wizards are more complex in that they allow different traversal paths based on the information provided by the user. The \l{dialogs/licensewizard}{License Wizard} example illustrates this. It provides five wizard pages; depending on which options are selected, the user can reach different pages. \image licensewizard-flow.png In complex wizards, pages are identified by IDs. These IDs are typically defined using an enum. For example: \snippet dialogs/licensewizard/licensewizard.h 0 \dots \snippet dialogs/licensewizard/licensewizard.h 2 \dots \snippet dialogs/licensewizard/licensewizard.h 3 The pages are inserted using setPage(), which takes an ID and an instance of QWizardPage (or of a subclass): \snippet dialogs/licensewizard/licensewizard.cpp 1 \dots \snippet dialogs/licensewizard/licensewizard.cpp 8 By default, the pages are shown in increasing ID order. To provide a dynamic order that depends on the options chosen by the user, we must reimplement QWizardPage::nextId(). For example: \snippet dialogs/licensewizard/licensewizard.cpp 18 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 23 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 24 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 25 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 26 It would also be possible to put all the logic in one place, in a QWizard::nextId() reimplementation. For example: \snippet code/src_gui_dialogs_qwizard.cpp 0 To start at another page than the page with the lowest ID, call setStartId(). To test whether a page has been visited or not, call hasVisitedPage(). For example: \snippet dialogs/licensewizard/licensewizard.cpp 27 \sa QWizardPage, {Class Wizard Example}, {License Wizard Example} */ /*! \enum QWizard::WizardButton This enum specifies the buttons in a wizard. \value BackButton The \uicontrol Back button (\uicontrol {Go Back} on Mac OS X) \value NextButton The \uicontrol Next button (\uicontrol Continue on Mac OS X) \value CommitButton The \uicontrol Commit button \value FinishButton The \uicontrol Finish button (\uicontrol Done on Mac OS X) \value CancelButton The \uicontrol Cancel button (see also NoCancelButton) \value HelpButton The \uicontrol Help button (see also HaveHelpButton) \value CustomButton1 The first user-defined button (see also HaveCustomButton1) \value CustomButton2 The second user-defined button (see also HaveCustomButton2) \value CustomButton3 The third user-defined button (see also HaveCustomButton3) The following value is only useful when calling setButtonLayout(): \value Stretch A horizontal stretch in the button layout \omitvalue NoButton \omitvalue NStandardButtons \omitvalue NButtons \sa setButton(), setButtonText(), setButtonLayout(), customButtonClicked() */ /*! \enum QWizard::WizardPixmap This enum specifies the pixmaps that can be associated with a page. \value WatermarkPixmap The tall pixmap on the left side of a ClassicStyle or ModernStyle page \value LogoPixmap The small pixmap on the right side of a ClassicStyle or ModernStyle page header \value BannerPixmap The pixmap that occupies the background of a ModernStyle page header \value BackgroundPixmap The pixmap that occupies the background of a MacStyle wizard \omitvalue NPixmaps \sa setPixmap(), QWizardPage::setPixmap(), {Elements of a Wizard Page} */ /*! \enum QWizard::WizardStyle This enum specifies the different looks supported by QWizard. \value ClassicStyle Classic Windows look \value ModernStyle Modern Windows look \value MacStyle Mac OS X look \value AeroStyle Windows Aero look \omitvalue NStyles \sa setWizardStyle(), WizardOption, {Wizard Look and Feel} */ /*! \enum QWizard::WizardOption This enum specifies various options that affect the look and feel of a wizard. \value IndependentPages The pages are independent of each other (i.e., they don't derive values from each other). \value IgnoreSubTitles Don't show any subtitles, even if they are set. \value ExtendedWatermarkPixmap Extend any WatermarkPixmap all the way down to the window's edge. \value NoDefaultButton Don't make the \uicontrol Next or \uicontrol Finish button the dialog's \l{QPushButton::setDefault()}{default button}. \value NoBackButtonOnStartPage Don't show the \uicontrol Back button on the start page. \value NoBackButtonOnLastPage Don't show the \uicontrol Back button on the last page. \value DisabledBackButtonOnLastPage Disable the \uicontrol Back button on the last page. \value HaveNextButtonOnLastPage Show the (disabled) \uicontrol Next button on the last page. \value HaveFinishButtonOnEarlyPages Show the (disabled) \uicontrol Finish button on non-final pages. \value NoCancelButton Don't show the \uicontrol Cancel button. \value CancelButtonOnLeft Put the \uicontrol Cancel button on the left of \uicontrol Back (rather than on the right of \uicontrol Finish or \uicontrol Next). \value HaveHelpButton Show the \uicontrol Help button. \value HelpButtonOnRight Put the \uicontrol Help button on the far right of the button layout (rather than on the far left). \value HaveCustomButton1 Show the first user-defined button (CustomButton1). \value HaveCustomButton2 Show the second user-defined button (CustomButton2). \value HaveCustomButton3 Show the third user-defined button (CustomButton3). \sa setOptions(), setOption(), testOption() */ /*! Constructs a wizard with the given \a parent and window \a flags. \sa parent(), windowFlags() */ QWizard::QWizard(QWidget *parent, Qt::WindowFlags flags) : QDialog(*new QWizardPrivate, parent, flags) { Q_D(QWizard); d->init(); #ifdef Q_OS_WINCE if (!qt_wince_is_mobile()) setWindowFlags(windowFlags() & ~Qt::WindowOkButtonHint); #endif } /*! Destroys the wizard and its pages, releasing any allocated resources. */ QWizard::~QWizard() { Q_D(QWizard); delete d->buttonLayout; } /*! Adds the given \a page to the wizard, and returns the page's ID. The ID is guaranteed to be larger than any other ID in the QWizard so far. \sa setPage(), page(), pageAdded() */ int QWizard::addPage(QWizardPage *page) { Q_D(QWizard); int theid = 0; if (!d->pageMap.isEmpty()) theid = (d->pageMap.constEnd() - 1).key() + 1; setPage(theid, page); return theid; } /*! \fn void QWizard::setPage(int id, QWizardPage *page) Adds the given \a page to the wizard with the given \a id. \note Adding a page may influence the value of the startId property in case it was not set explicitly. \sa addPage(), page(), pageAdded() */ void QWizard::setPage(int theid, QWizardPage *page) { Q_D(QWizard); if (!page) { qWarning("QWizard::setPage: Cannot insert null page"); return; } if (theid == -1) { qWarning("QWizard::setPage: Cannot insert page with ID -1"); return; } if (d->pageMap.contains(theid)) { qWarning("QWizard::setPage: Page with duplicate ID %d ignored", theid); return; } page->setParent(d->pageFrame); QVector &pendingFields = page->d_func()->pendingFields; for (int i = 0; i < pendingFields.count(); ++i) d->addField(pendingFields.at(i)); pendingFields.clear(); connect(page, SIGNAL(completeChanged()), this, SLOT(_q_updateButtonStates())); d->pageMap.insert(theid, page); page->d_func()->wizard = this; int n = d->pageVBoxLayout->count(); // disable layout to prevent layout updates while adding bool pageVBoxLayoutEnabled = d->pageVBoxLayout->isEnabled(); d->pageVBoxLayout->setEnabled(false); d->pageVBoxLayout->insertWidget(n - 1, page); // hide new page and reset layout to old status page->hide(); d->pageVBoxLayout->setEnabled(pageVBoxLayoutEnabled); if (!d->startSetByUser && d->pageMap.constBegin().key() == theid) d->start = theid; emit pageAdded(theid); } /*! Removes the page with the given \a id. cleanupPage() will be called if necessary. \note Removing a page may influence the value of the startId property. \since 4.5 \sa addPage(), setPage(), pageRemoved(), startId() */ void QWizard::removePage(int id) { Q_D(QWizard); QWizardPage *removedPage = 0; // update startItem accordingly if (d->pageMap.count() > 0) { // only if we have any pages if (d->start == id) { const int firstId = d->pageMap.constBegin().key(); if (firstId == id) { if (d->pageMap.count() > 1) d->start = (++d->pageMap.constBegin()).key(); // secondId else d->start = -1; // removing the last page } else { // startSetByUser has to be "true" here d->start = firstId; } d->startSetByUser = false; } } if (d->pageMap.contains(id)) emit pageRemoved(id); if (!d->history.contains(id)) { // Case 1: removing a page not in the history removedPage = d->pageMap.take(id); d->updateCurrentPage(); } else if (id != d->current) { // Case 2: removing a page in the history before the current page removedPage = d->pageMap.take(id); d->history.removeOne(id); d->_q_updateButtonStates(); } else if (d->history.count() == 1) { // Case 3: removing the current page which is the first (and only) one in the history d->reset(); removedPage = d->pageMap.take(id); if (d->pageMap.isEmpty()) d->updateCurrentPage(); else restart(); } else { // Case 4: removing the current page which is not the first one in the history back(); removedPage = d->pageMap.take(id); d->updateCurrentPage(); } if (removedPage) { if (d->initialized.contains(id)) { cleanupPage(id); d->initialized.remove(id); } d->pageVBoxLayout->removeWidget(removedPage); for (int i = d->fields.count() - 1; i >= 0; --i) { if (d->fields.at(i).page == removedPage) { removedPage->d_func()->pendingFields += d->fields.at(i); d->removeFieldAt(i); } } } } /*! \fn QWizardPage *QWizard::page(int id) const Returns the page with the given \a id, or 0 if there is no such page. \sa addPage(), setPage() */ QWizardPage *QWizard::page(int theid) const { Q_D(const QWizard); return d->pageMap.value(theid); } /*! \fn bool QWizard::hasVisitedPage(int id) const Returns \c true if the page history contains page \a id; otherwise, returns \c false. Pressing \uicontrol Back marks the current page as "unvisited" again. \sa visitedPages() */ bool QWizard::hasVisitedPage(int theid) const { Q_D(const QWizard); return d->history.contains(theid); } /*! Returns the list of IDs of visited pages, in the order in which the pages were visited. Pressing \uicontrol Back marks the current page as "unvisited" again. \sa hasVisitedPage() */ QList QWizard::visitedPages() const { Q_D(const QWizard); return d->history; } /*! Returns the list of page IDs. \since 4.5 */ QList QWizard::pageIds() const { Q_D(const QWizard); return d->pageMap.keys(); } /*! \property QWizard::startId \brief the ID of the first page If this property isn't explicitly set, this property defaults to the lowest page ID in this wizard, or -1 if no page has been inserted yet. \sa restart(), nextId() */ void QWizard::setStartId(int theid) { Q_D(QWizard); int newStart = theid; if (theid == -1) newStart = d->pageMap.count() ? d->pageMap.constBegin().key() : -1; if (d->start == newStart) { d->startSetByUser = theid != -1; return; } if (!d->pageMap.contains(newStart)) { qWarning("QWizard::setStartId: Invalid page ID %d", newStart); return; } d->start = newStart; d->startSetByUser = theid != -1; } int QWizard::startId() const { Q_D(const QWizard); return d->start; } /*! Returns a pointer to the current page, or 0 if there is no current page (e.g., before the wizard is shown). This is equivalent to calling page(currentId()). \sa page(), currentId(), restart() */ QWizardPage *QWizard::currentPage() const { Q_D(const QWizard); return page(d->current); } /*! \property QWizard::currentId \brief the ID of the current page This property cannot be set directly. To change the current page, call next(), back(), or restart(). By default, this property has a value of -1, indicating that no page is currently shown. \sa currentPage() */ int QWizard::currentId() const { Q_D(const QWizard); return d->current; } /*! Sets the value of the field called \a name to \a value. This function can be used to set fields on any page of the wizard. \sa QWizardPage::registerField(), QWizardPage::setField(), field() */ void QWizard::setField(const QString &name, const QVariant &value) { Q_D(QWizard); int index = d->fieldIndexMap.value(name, -1); if (index != -1) { const QWizardField &field = d->fields.at(index); if (!field.object->setProperty(field.property, value)) qWarning("QWizard::setField: Couldn't write to property '%s'", field.property.constData()); return; } qWarning("QWizard::setField: No such field '%s'", qPrintable(name)); } /*! Returns the value of the field called \a name. This function can be used to access fields on any page of the wizard. \sa QWizardPage::registerField(), QWizardPage::field(), setField() */ QVariant QWizard::field(const QString &name) const { Q_D(const QWizard); int index = d->fieldIndexMap.value(name, -1); if (index != -1) { const QWizardField &field = d->fields.at(index); return field.object->property(field.property); } qWarning("QWizard::field: No such field '%s'", qPrintable(name)); return QVariant(); } /*! \property QWizard::wizardStyle \brief the look and feel of the wizard By default, QWizard uses the AeroStyle on a Windows Vista system with alpha compositing enabled, regardless of the current widget style. If this is not the case, the default wizard style depends on the current widget style as follows: MacStyle is the default if the current widget style is QMacStyle, ModernStyle is the default if the current widget style is QWindowsStyle, and ClassicStyle is the default in all other cases. \sa {Wizard Look and Feel}, options */ void QWizard::setWizardStyle(WizardStyle style) { Q_D(QWizard); const bool styleChange = style != d->wizStyle; #if !defined(QT_NO_STYLE_WINDOWSVISTA) const bool aeroStyleChange = d->vistaInitPending || d->vistaStateChanged || (styleChange && (style == AeroStyle || d->wizStyle == AeroStyle)); d->vistaStateChanged = false; d->vistaInitPending = false; #endif if (styleChange #if !defined(QT_NO_STYLE_WINDOWSVISTA) || aeroStyleChange #endif ) { d->disableUpdates(); d->wizStyle = style; d->updateButtonTexts(); d->updateLayout(); updateGeometry(); d->enableUpdates(); #if !defined(QT_NO_STYLE_WINDOWSVISTA) // Delay initialization when activating Aero style fails due to missing native window. if (aeroStyleChange && !d->handleAeroStyleChange() && d->wizStyle == AeroStyle) d->vistaInitPending = true; #endif } } QWizard::WizardStyle QWizard::wizardStyle() const { Q_D(const QWizard); return d->wizStyle; } /*! Sets the given \a option to be enabled if \a on is true; otherwise, clears the given \a option. \sa options, testOption(), setWizardStyle() */ void QWizard::setOption(WizardOption option, bool on) { Q_D(QWizard); if (!(d->opts & option) != !on) setOptions(d->opts ^ option); } /*! Returns \c true if the given \a option is enabled; otherwise, returns false. \sa options, setOption(), setWizardStyle() */ bool QWizard::testOption(WizardOption option) const { Q_D(const QWizard); return (d->opts & option) != 0; } /*! \property QWizard::options \brief the various options that affect the look and feel of the wizard By default, the following options are set (depending on the platform): \list \li Windows: HelpButtonOnRight. \li Mac OS X: NoDefaultButton and NoCancelButton. \li X11 and QWS (Qt for Embedded Linux): none. \endlist \sa wizardStyle */ void QWizard::setOptions(WizardOptions options) { Q_D(QWizard); WizardOptions changed = (options ^ d->opts); if (!changed) return; d->disableUpdates(); d->opts = options; if ((changed & IndependentPages) && !(d->opts & IndependentPages)) d->cleanupPagesNotInHistory(); if (changed & (NoDefaultButton | HaveHelpButton | HelpButtonOnRight | NoCancelButton | CancelButtonOnLeft | HaveCustomButton1 | HaveCustomButton2 | HaveCustomButton3)) { d->updateButtonLayout(); } else if (changed & (NoBackButtonOnStartPage | NoBackButtonOnLastPage | HaveNextButtonOnLastPage | HaveFinishButtonOnEarlyPages | DisabledBackButtonOnLastPage)) { d->_q_updateButtonStates(); } d->enableUpdates(); d->updateLayout(); } QWizard::WizardOptions QWizard::options() const { Q_D(const QWizard); return d->opts; } /*! Sets the text on button \a which to be \a text. By default, the text on buttons depends on the wizardStyle. For example, on Mac OS X, the \uicontrol Next button is called \uicontrol Continue. To add extra buttons to the wizard (e.g., a \uicontrol Print button), one way is to call setButtonText() with CustomButton1, CustomButton2, or CustomButton3 to set their text, and make the buttons visible using the HaveCustomButton1, HaveCustomButton2, and/or HaveCustomButton3 options. Button texts may also be set on a per-page basis using QWizardPage::setButtonText(). \sa setButton(), button(), setButtonLayout(), setOptions(), QWizardPage::setButtonText() */ void QWizard::setButtonText(WizardButton which, const QString &text) { Q_D(QWizard); if (!d->ensureButton(which)) return; d->buttonCustomTexts.insert(which, text); if (!currentPage() || !currentPage()->d_func()->buttonCustomTexts.contains(which)) d->btns[which]->setText(text); } /*! Returns the text on button \a which. If a text has ben set using setButtonText(), this text is returned. By default, the text on buttons depends on the wizardStyle. For example, on Mac OS X, the \uicontrol Next button is called \uicontrol Continue. \sa button(), setButton(), setButtonText(), QWizardPage::buttonText(), QWizardPage::setButtonText() */ QString QWizard::buttonText(WizardButton which) const { Q_D(const QWizard); if (!d->ensureButton(which)) return QString(); if (d->buttonCustomTexts.contains(which)) return d->buttonCustomTexts.value(which); const QString defText = buttonDefaultText(d->wizStyle, which, d); if(!defText.isNull()) return defText; return d->btns[which]->text(); } /*! Sets the order in which buttons are displayed to \a layout, where \a layout is a list of \l{WizardButton}s. The default layout depends on the options (e.g., whether HelpButtonOnRight) that are set. You can call this function if you need more control over the buttons' layout than what \l options already provides. You can specify horizontal stretches in the layout using \l Stretch. Example: \snippet code/src_gui_dialogs_qwizard.cpp 1 \sa setButton(), setButtonText(), setOptions() */ void QWizard::setButtonLayout(const QList &layout) { Q_D(QWizard); for (int i = 0; i < layout.count(); ++i) { WizardButton button1 = layout.at(i); if (button1 == NoButton || button1 == Stretch) continue; if (!d->ensureButton(button1)) return; // O(n^2), but n is very small for (int j = 0; j < i; ++j) { WizardButton button2 = layout.at(j); if (button2 == button1) { qWarning("QWizard::setButtonLayout: Duplicate button in layout"); return; } } } d->buttonsHaveCustomLayout = true; d->buttonsCustomLayout = layout; d->updateButtonLayout(); } /*! Sets the button corresponding to role \a which to \a button. To add extra buttons to the wizard (e.g., a \uicontrol Print button), one way is to call setButton() with CustomButton1 to CustomButton3, and make the buttons visible using the HaveCustomButton1 to HaveCustomButton3 options. \sa setButtonText(), setButtonLayout(), options */ void QWizard::setButton(WizardButton which, QAbstractButton *button) { Q_D(QWizard); if (uint(which) >= NButtons || d->btns[which] == button) return; if (QAbstractButton *oldButton = d->btns[which]) { d->buttonLayout->removeWidget(oldButton); delete oldButton; } d->btns[which] = button; if (button) { button->setParent(d->antiFlickerWidget); d->buttonCustomTexts.insert(which, button->text()); d->connectButton(which); } else { d->buttonCustomTexts.remove(which); // ### what about page-specific texts set for 'which' d->ensureButton(which); // (QWizardPage::setButtonText())? Clear them as well? } d->updateButtonLayout(); } /*! Returns the button corresponding to role \a which. \sa setButton(), setButtonText() */ QAbstractButton *QWizard::button(WizardButton which) const { Q_D(const QWizard); #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (d->wizStyle == AeroStyle && which == BackButton) return d->vistaHelper->backButton(); #endif if (!d->ensureButton(which)) return 0; return d->btns[which]; } /*! \property QWizard::titleFormat \brief the text format used by page titles The default format is Qt::AutoText. \sa QWizardPage::title, subTitleFormat */ void QWizard::setTitleFormat(Qt::TextFormat format) { Q_D(QWizard); d->titleFmt = format; d->updateLayout(); } Qt::TextFormat QWizard::titleFormat() const { Q_D(const QWizard); return d->titleFmt; } /*! \property QWizard::subTitleFormat \brief the text format used by page subtitles The default format is Qt::AutoText. \sa QWizardPage::title, titleFormat */ void QWizard::setSubTitleFormat(Qt::TextFormat format) { Q_D(QWizard); d->subTitleFmt = format; d->updateLayout(); } Qt::TextFormat QWizard::subTitleFormat() const { Q_D(const QWizard); return d->subTitleFmt; } /*! Sets the pixmap for role \a which to \a pixmap. The pixmaps are used by QWizard when displaying a page. Which pixmaps are actually used depend on the \l{Wizard Look and Feel}{wizard style}. Pixmaps can also be set for a specific page using QWizardPage::setPixmap(). \sa QWizardPage::setPixmap(), {Elements of a Wizard Page} */ void QWizard::setPixmap(WizardPixmap which, const QPixmap &pixmap) { Q_D(QWizard); Q_ASSERT(uint(which) < NPixmaps); d->defaultPixmaps[which] = pixmap; d->updatePixmap(which); } /*! Returns the pixmap set for role \a which. By default, the only pixmap that is set is the BackgroundPixmap on Mac OS X. \sa QWizardPage::pixmap(), {Elements of a Wizard Page} */ QPixmap QWizard::pixmap(WizardPixmap which) const { Q_D(const QWizard); Q_ASSERT(uint(which) < NPixmaps); #ifdef Q_OS_MACX if (which == BackgroundPixmap && d->defaultPixmaps[BackgroundPixmap].isNull()) d->defaultPixmaps[BackgroundPixmap] = d->findDefaultBackgroundPixmap(); #endif return d->defaultPixmaps[which]; } /*! Sets the default property for \a className to be \a property, and the associated change signal to be \a changedSignal. The default property is used when an instance of \a className (or of one of its subclasses) is passed to QWizardPage::registerField() and no property is specified. QWizard knows the most common Qt widgets. For these (or their subclasses), you don't need to specify a \a property or a \a changedSignal. The table below lists these widgets: \table \header \li Widget \li Property \li Change Notification Signal \row \li QAbstractButton \li bool \l{QAbstractButton::}{checked} \li \l{QAbstractButton::}{toggled()} \row \li QAbstractSlider \li int \l{QAbstractSlider::}{value} \li \l{QAbstractSlider::}{valueChanged()} \row \li QComboBox \li int \l{QComboBox::}{currentIndex} \li \l{QComboBox::}{currentIndexChanged()} \row \li QDateTimeEdit \li QDateTime \l{QDateTimeEdit::}{dateTime} \li \l{QDateTimeEdit::}{dateTimeChanged()} \row \li QLineEdit \li QString \l{QLineEdit::}{text} \li \l{QLineEdit::}{textChanged()} \row \li QListWidget \li int \l{QListWidget::}{currentRow} \li \l{QListWidget::}{currentRowChanged()} \row \li QSpinBox \li int \l{QSpinBox::}{value} \li \l{QSpinBox::}{valueChanged()} \endtable \sa QWizardPage::registerField() */ void QWizard::setDefaultProperty(const char *className, const char *property, const char *changedSignal) { Q_D(QWizard); for (int i = d->defaultPropertyTable.count() - 1; i >= 0; --i) { if (qstrcmp(d->defaultPropertyTable.at(i).className, className) == 0) { d->defaultPropertyTable.remove(i); break; } } d->defaultPropertyTable.append(QWizardDefaultProperty(className, property, changedSignal)); } /*! \since 4.7 Sets the given \a widget to be shown on the left side of the wizard. For styles which use the WatermarkPixmap (ClassicStyle and ModernStyle) the side widget is displayed on top of the watermark, for other styles or when the watermark is not provided the side widget is displayed on the left side of the wizard. Passing 0 shows no side widget. When the \a widget is not 0 the wizard reparents it. Any previous side widget is hidden. You may call setSideWidget() with the same widget at different times. All widgets set here will be deleted by the wizard when it is destroyed unless you separately reparent the widget after setting some other side widget (or 0). By default, no side widget is present. */ void QWizard::setSideWidget(QWidget *widget) { Q_D(QWizard); d->sideWidget = widget; if (d->watermarkLabel) { d->watermarkLabel->setSideWidget(widget); d->updateLayout(); } } /*! \since 4.7 Returns the widget on the left side of the wizard or 0. By default, no side widget is present. */ QWidget *QWizard::sideWidget() const { Q_D(const QWizard); return d->sideWidget; } /*! \reimp */ void QWizard::setVisible(bool visible) { Q_D(QWizard); if (visible) { if (d->current == -1) restart(); } QDialog::setVisible(visible); } /*! \reimp */ QSize QWizard::sizeHint() const { Q_D(const QWizard); QSize result = d->mainLayout->totalSizeHint(); QSize extra(500, 360); if (d->wizStyle == MacStyle && d->current != -1) { QSize pixmap(currentPage()->pixmap(BackgroundPixmap).size()); extra.setWidth(616); if (!pixmap.isNull()) { extra.setHeight(pixmap.height()); /* The width isn't always reliable as a size hint, as some wizard backgrounds just cover the leftmost area. Use a rule of thumb to determine if the width is reliable or not. */ if (pixmap.width() >= pixmap.height()) extra.setWidth(pixmap.width()); } } return result.expandedTo(extra); } /*! \fn void QWizard::currentIdChanged(int id) This signal is emitted when the current page changes, with the new current \a id. \sa currentId(), currentPage() */ /*! \fn void QWizard::pageAdded(int id) \since 4.7 This signal is emitted whenever a page is added to the wizard. The page's \a id is passed as parameter. \sa addPage(), setPage(), startId() */ /*! \fn void QWizard::pageRemoved(int id) \since 4.7 This signal is emitted whenever a page is removed from the wizard. The page's \a id is passed as parameter. \sa removePage(), startId() */ /*! \fn void QWizard::helpRequested() This signal is emitted when the user clicks the \uicontrol Help button. By default, no \uicontrol Help button is shown. Call setOption(HaveHelpButton, true) to have one. Example: \snippet dialogs/licensewizard/licensewizard.cpp 0 \dots \snippet dialogs/licensewizard/licensewizard.cpp 5 \snippet dialogs/licensewizard/licensewizard.cpp 7 \dots \snippet dialogs/licensewizard/licensewizard.cpp 8 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 10 \dots \snippet dialogs/licensewizard/licensewizard.cpp 12 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 14 \codeline \snippet dialogs/licensewizard/licensewizard.cpp 15 \sa customButtonClicked() */ /*! \fn void QWizard::customButtonClicked(int which) This signal is emitted when the user clicks a custom button. \a which can be CustomButton1, CustomButton2, or CustomButton3. By default, no custom button is shown. Call setOption() with HaveCustomButton1, HaveCustomButton2, or HaveCustomButton3 to have one, and use setButtonText() or setButton() to configure it. \sa helpRequested() */ /*! Goes back to the previous page. This is equivalent to pressing the \uicontrol Back button. \sa next(), accept(), reject(), restart() */ void QWizard::back() { Q_D(QWizard); int n = d->history.count() - 2; if (n < 0) return; d->switchToPage(d->history.at(n), QWizardPrivate::Backward); } /*! Advances to the next page. This is equivalent to pressing the \uicontrol Next or \uicontrol Commit button. \sa nextId(), back(), accept(), reject(), restart() */ void QWizard::next() { Q_D(QWizard); if (d->current == -1) return; if (validateCurrentPage()) { int next = nextId(); if (next != -1) { if (d->history.contains(next)) { qWarning("QWizard::next: Page %d already met", next); return; } if (!d->pageMap.contains(next)) { qWarning("QWizard::next: No such page %d", next); return; } d->switchToPage(next, QWizardPrivate::Forward); } } } /*! Restarts the wizard at the start page. This function is called automatically when the wizard is shown. \sa startId() */ void QWizard::restart() { Q_D(QWizard); d->disableUpdates(); d->reset(); d->switchToPage(startId(), QWizardPrivate::Forward); d->enableUpdates(); } /*! \reimp */ bool QWizard::event(QEvent *event) { Q_D(QWizard); if (event->type() == QEvent::StyleChange) { // Propagate style d->setStyle(style()); d->updateLayout(); } #if !defined(QT_NO_STYLE_WINDOWSVISTA) else if (event->type() == QEvent::Show && d->vistaInitPending) { d->vistaInitPending = false; // Do not force AeroStyle when in Classic theme. // Note that d->handleAeroStyleChange() needs to be called in any case as it does some // necessary initialization, like ensures that the Aero specific back button is hidden if // Aero theme isn't active. if (QVistaHelper::vistaState() != QVistaHelper::Classic) d->wizStyle = AeroStyle; d->handleAeroStyleChange(); } else if (d->isVistaThemeEnabled()) { if (event->type() == QEvent::Resize || event->type() == QEvent::LayoutDirectionChange) { const int buttonLeft = (layoutDirection() == Qt::RightToLeft ? width() - d->vistaHelper->backButton()->sizeHint().width() : 0); d->vistaHelper->backButton()->move(buttonLeft, d->vistaHelper->backButton()->y()); } d->vistaHelper->mouseEvent(event); } #endif return QDialog::event(event); } /*! \reimp */ void QWizard::resizeEvent(QResizeEvent *event) { Q_D(QWizard); int heightOffset = 0; #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (d->isVistaThemeEnabled()) { heightOffset = d->vistaHelper->topOffset(); if (d->isVistaThemeEnabled(QVistaHelper::VistaAero)) heightOffset += d->vistaHelper->titleBarSize(); } #endif d->antiFlickerWidget->resize(event->size().width(), event->size().height() - heightOffset); #if !defined(QT_NO_STYLE_WINDOWSVISTA) if (d->isVistaThemeEnabled()) d->vistaHelper->resizeEvent(event); #endif QDialog::resizeEvent(event); } /*! \reimp */ void QWizard::paintEvent(QPaintEvent * event) { Q_D(QWizard); if (d->wizStyle == MacStyle && currentPage()) { QPixmap backgroundPixmap = currentPage()->pixmap(BackgroundPixmap); if (backgroundPixmap.isNull()) return; QPainter painter(this); painter.drawPixmap(0, (height() - backgroundPixmap.height()) / 2, backgroundPixmap); } #if !defined(QT_NO_STYLE_WINDOWSVISTA) else if (d->isVistaThemeEnabled()) { if (d->isVistaThemeEnabled(QVistaHelper::VistaBasic)) { QPainter painter(this); QColor color = d->vistaHelper->basicWindowFrameColor(); painter.fillRect(0, 0, width(), QVistaHelper::topOffset(), color); } d->vistaHelper->paintEvent(event); } #else Q_UNUSED(event); #endif } #if defined(Q_OS_WIN) /*! \reimp */ bool QWizard::nativeEvent(const QByteArray &eventType, void *message, long *result) { #if !defined(QT_NO_STYLE_WINDOWSVISTA) Q_D(QWizard); if (d->isVistaThemeEnabled() && eventType == QByteArrayLiteral("windows_generic_MSG")) { MSG *windowsMessage = static_cast(message); const bool winEventResult = d->vistaHelper->handleWinEvent(windowsMessage, result); if (QVistaHelper::vistaState() != d->vistaState) { d->vistaState = QVistaHelper::vistaState(); d->vistaStateChanged = true; setWizardStyle(AeroStyle); } return winEventResult; } else { return QDialog::nativeEvent(eventType, message, result); } #else return QDialog::nativeEvent(eventType, message, result); #endif } #endif /*! \reimp */ void QWizard::done(int result) { Q_D(QWizard); // canceling leaves the wizard in a known state if (result == Rejected) { d->reset(); } else { if (!validateCurrentPage()) return; } QDialog::done(result); } /*! \fn void QWizard::initializePage(int id) This virtual function is called by QWizard to prepare page \a id just before it is shown either as a result of QWizard::restart() being called, or as a result of the user clicking \uicontrol Next. (However, if the \l QWizard::IndependentPages option is set, this function is only called the first time the page is shown.) By reimplementing this function, you can ensure that the page's fields are properly initialized based on fields from previous pages. The default implementation calls QWizardPage::initializePage() on page(\a id). \sa QWizardPage::initializePage(), cleanupPage() */ void QWizard::initializePage(int theid) { QWizardPage *page = this->page(theid); if (page) page->initializePage(); } /*! \fn void QWizard::cleanupPage(int id) This virtual function is called by QWizard to clean up page \a id just before the user leaves it by clicking \uicontrol Back (unless the \l QWizard::IndependentPages option is set). The default implementation calls QWizardPage::cleanupPage() on page(\a id). \sa QWizardPage::cleanupPage(), initializePage() */ void QWizard::cleanupPage(int theid) { QWizardPage *page = this->page(theid); if (page) page->cleanupPage(); } /*! This virtual function is called by QWizard when the user clicks \uicontrol Next or \uicontrol Finish to perform some last-minute validation. If it returns \c true, the next page is shown (or the wizard finishes); otherwise, the current page stays up. The default implementation calls QWizardPage::validatePage() on the currentPage(). When possible, it is usually better style to disable the \uicontrol Next or \uicontrol Finish button (by specifying \l{mandatory fields} or by reimplementing QWizardPage::isComplete()) than to reimplement validateCurrentPage(). \sa QWizardPage::validatePage(), currentPage() */ bool QWizard::validateCurrentPage() { QWizardPage *page = currentPage(); if (!page) return true; return page->validatePage(); } /*! This virtual function is called by QWizard to find out which page to show when the user clicks the \uicontrol Next button. The return value is the ID of the next page, or -1 if no page follows. The default implementation calls QWizardPage::nextId() on the currentPage(). By reimplementing this function, you can specify a dynamic page order. \sa QWizardPage::nextId(), currentPage() */ int QWizard::nextId() const { const QWizardPage *page = currentPage(); if (!page) return -1; return page->nextId(); } /*! \class QWizardPage \since 4.3 \brief The QWizardPage class is the base class for wizard pages. \inmodule QtWidgets QWizard represents a wizard. Each page is a QWizardPage. When you create your own wizards, you can use QWizardPage directly, or you can subclass it for more control. A page has the following attributes, which are rendered by QWizard: a \l title, a \l subTitle, and a \l{setPixmap()}{set of pixmaps}. See \l{Elements of a Wizard Page} for details. Once a page is added to the wizard (using QWizard::addPage() or QWizard::setPage()), wizard() returns a pointer to the associated QWizard object. Page provides five virtual functions that can be reimplemented to provide custom behavior: \list \li initializePage() is called to initialize the page's contents when the user clicks the wizard's \uicontrol Next button. If you want to derive the page's default from what the user entered on previous pages, this is the function to reimplement. \li cleanupPage() is called to reset the page's contents when the user clicks the wizard's \uicontrol Back button. \li validatePage() validates the page when the user clicks \uicontrol Next or \uicontrol Finish. It is often used to show an error message if the user has entered incomplete or invalid information. \li nextId() returns the ID of the next page. It is useful when \l{creating non-linear wizards}, which allow different traversal paths based on the information provided by the user. \li isComplete() is called to determine whether the \uicontrol Next and/or \uicontrol Finish button should be enabled or disabled. If you reimplement isComplete(), also make sure that completeChanged() is emitted whenever the complete state changes. \endlist Normally, the \uicontrol Next button and the \uicontrol Finish button of a wizard are mutually exclusive. If isFinalPage() returns \c true, \uicontrol Finish is available; otherwise, \uicontrol Next is available. By default, isFinalPage() is true only when nextId() returns -1. If you want to show \uicontrol Next and \uicontrol Final simultaneously for a page (letting the user perform an "early finish"), call setFinalPage(true) on that page. For wizards that support early finishes, you might also want to set the \l{QWizard::}{HaveNextButtonOnLastPage} and \l{QWizard::}{HaveFinishButtonOnEarlyPages} options on the wizard. In many wizards, the contents of a page may affect the default values of the fields of a later page. To make it easy to communicate between pages, QWizard supports a \l{Registering and Using Fields}{"field" mechanism} that allows you to register a field (e.g., a QLineEdit) on a page and to access its value from any page. Fields are global to the entire wizard and make it easy for any single page to access information stored by another page, without having to put all the logic in QWizard or having the pages know explicitly about each other. Fields are registered using registerField() and can be accessed at any time using field() and setField(). \sa QWizard, {Class Wizard Example}, {License Wizard Example} */ /*! Constructs a wizard page with the given \a parent. When the page is inserted into a wizard using QWizard::addPage() or QWizard::setPage(), the parent is automatically set to be the wizard. \sa wizard() */ QWizardPage::QWizardPage(QWidget *parent) : QWidget(*new QWizardPagePrivate, parent, 0) { connect(this, SIGNAL(completeChanged()), this, SLOT(_q_updateCachedCompleteState())); } /*! Destructor. */ QWizardPage::~QWizardPage() { } /*! \property QWizardPage::title \brief the title of the page The title is shown by the QWizard, above the actual page. All pages should have a title. The title may be plain text or HTML, depending on the value of the \l{QWizard::titleFormat} property. By default, this property contains an empty string. \sa subTitle, {Elements of a Wizard Page} */ void QWizardPage::setTitle(const QString &title) { Q_D(QWizardPage); d->title = title; if (d->wizard && d->wizard->currentPage() == this) d->wizard->d_func()->updateLayout(); } QString QWizardPage::title() const { Q_D(const QWizardPage); return d->title; } /*! \property QWizardPage::subTitle \brief the subtitle of the page The subtitle is shown by the QWizard, between the title and the actual page. Subtitles are optional. In \l{QWizard::ClassicStyle}{ClassicStyle} and \l{QWizard::ModernStyle}{ModernStyle}, using subtitles is necessary to make the header appear. In \l{QWizard::MacStyle}{MacStyle}, the subtitle is shown as a text label just above the actual page. The subtitle may be plain text or HTML, depending on the value of the \l{QWizard::subTitleFormat} property. By default, this property contains an empty string. \sa title, QWizard::IgnoreSubTitles, {Elements of a Wizard Page} */ void QWizardPage::setSubTitle(const QString &subTitle) { Q_D(QWizardPage); d->subTitle = subTitle; if (d->wizard && d->wizard->currentPage() == this) d->wizard->d_func()->updateLayout(); } QString QWizardPage::subTitle() const { Q_D(const QWizardPage); return d->subTitle; } /*! Sets the pixmap for role \a which to \a pixmap. The pixmaps are used by QWizard when displaying a page. Which pixmaps are actually used depend on the \l{Wizard Look and Feel}{wizard style}. Pixmaps can also be set for the entire wizard using QWizard::setPixmap(), in which case they apply for all pages that don't specify a pixmap. \sa QWizard::setPixmap(), {Elements of a Wizard Page} */ void QWizardPage::setPixmap(QWizard::WizardPixmap which, const QPixmap &pixmap) { Q_D(QWizardPage); Q_ASSERT(uint(which) < QWizard::NPixmaps); d->pixmaps[which] = pixmap; if (d->wizard && d->wizard->currentPage() == this) d->wizard->d_func()->updatePixmap(which); } /*! Returns the pixmap set for role \a which. Pixmaps can also be set for the entire wizard using QWizard::setPixmap(), in which case they apply for all pages that don't specify a pixmap. \sa QWizard::pixmap(), {Elements of a Wizard Page} */ QPixmap QWizardPage::pixmap(QWizard::WizardPixmap which) const { Q_D(const QWizardPage); Q_ASSERT(uint(which) < QWizard::NPixmaps); const QPixmap &pixmap = d->pixmaps[which]; if (!pixmap.isNull()) return pixmap; if (wizard()) return wizard()->pixmap(which); return pixmap; } /*! This virtual function is called by QWizard::initializePage() to prepare the page just before it is shown either as a result of QWizard::restart() being called, or as a result of the user clicking \uicontrol Next. (However, if the \l QWizard::IndependentPages option is set, this function is only called the first time the page is shown.) By reimplementing this function, you can ensure that the page's fields are properly initialized based on fields from previous pages. For example: \snippet dialogs/classwizard/classwizard.cpp 17 The default implementation does nothing. \sa QWizard::initializePage(), cleanupPage(), QWizard::IndependentPages */ void QWizardPage::initializePage() { } /*! This virtual function is called by QWizard::cleanupPage() when the user leaves the page by clicking \uicontrol Back (unless the \l QWizard::IndependentPages option is set). The default implementation resets the page's fields to their original values (the values they had before initializePage() was called). \sa QWizard::cleanupPage(), initializePage(), QWizard::IndependentPages */ void QWizardPage::cleanupPage() { Q_D(QWizardPage); if (d->wizard) { QVector &fields = d->wizard->d_func()->fields; for (int i = 0; i < fields.count(); ++i) { const QWizardField &field = fields.at(i); if (field.page == this) field.object->setProperty(field.property, field.initialValue); } } } /*! This virtual function is called by QWizard::validateCurrentPage() when the user clicks \uicontrol Next or \uicontrol Finish to perform some last-minute validation. If it returns \c true, the next page is shown (or the wizard finishes); otherwise, the current page stays up. The default implementation returns \c true. When possible, it is usually better style to disable the \uicontrol Next or \uicontrol Finish button (by specifying \l{mandatory fields} or reimplementing isComplete()) than to reimplement validatePage(). \sa QWizard::validateCurrentPage(), isComplete() */ bool QWizardPage::validatePage() { return true; } /*! This virtual function is called by QWizard to determine whether the \uicontrol Next or \uicontrol Finish button should be enabled or disabled. The default implementation returns \c true if all \l{mandatory fields} are filled; otherwise, it returns \c false. If you reimplement this function, make sure to emit completeChanged(), from the rest of your implementation, whenever the value of isComplete() changes. This ensures that QWizard updates the enabled or disabled state of its buttons. An example of the reimplementation is available \l{http://doc.qt.digia.com/qq/qq22-qwizard.html#validatebeforeitstoolate} {here}. \sa completeChanged(), isFinalPage() */ bool QWizardPage::isComplete() const { Q_D(const QWizardPage); if (!d->wizard) return true; const QVector &wizardFields = d->wizard->d_func()->fields; for (int i = wizardFields.count() - 1; i >= 0; --i) { const QWizardField &field = wizardFields.at(i); if (field.page == this && field.mandatory) { QVariant value = field.object->property(field.property); if (value == field.initialValue) return false; #ifndef QT_NO_LINEEDIT if (QLineEdit *lineEdit = qobject_cast(field.object)) { if (!lineEdit->hasAcceptableInput()) return false; } #endif #ifndef QT_NO_SPINBOX if (QAbstractSpinBox *spinBox = qobject_cast(field.object)) { if (!spinBox->hasAcceptableInput()) return false; } #endif } } return true; } /*! Explicitly sets this page to be final if \a finalPage is true. After calling setFinalPage(true), isFinalPage() returns \c true and the \uicontrol Finish button is visible (and enabled if isComplete() returns true). After calling setFinalPage(false), isFinalPage() returns \c true if nextId() returns -1; otherwise, it returns \c false. \sa isComplete(), QWizard::HaveFinishButtonOnEarlyPages */ void QWizardPage::setFinalPage(bool finalPage) { Q_D(QWizardPage); d->explicitlyFinal = finalPage; QWizard *wizard = this->wizard(); if (wizard && wizard->currentPage() == this) wizard->d_func()->updateCurrentPage(); } /*! This function is called by QWizard to determine whether the \uicontrol Finish button should be shown for this page or not. By default, it returns \c true if there is no next page (i.e., nextId() returns -1); otherwise, it returns \c false. By explicitly calling setFinalPage(true), you can let the user perform an "early finish". \sa isComplete(), QWizard::HaveFinishButtonOnEarlyPages */ bool QWizardPage::isFinalPage() const { Q_D(const QWizardPage); if (d->explicitlyFinal) return true; QWizard *wizard = this->wizard(); if (wizard && wizard->currentPage() == this) { // try to use the QWizard implementation if possible return wizard->nextId() == -1; } else { return nextId() == -1; } } /*! Sets this page to be a commit page if \a commitPage is true; otherwise, sets it to be a normal page. A commit page is a page that represents an action which cannot be undone by clicking \uicontrol Back or \uicontrol Cancel. A \uicontrol Commit button replaces the \uicontrol Next button on a commit page. Clicking this button simply calls QWizard::next() just like clicking \uicontrol Next does. A page entered directly from a commit page has its \uicontrol Back button disabled. \sa isCommitPage() */ void QWizardPage::setCommitPage(bool commitPage) { Q_D(QWizardPage); d->commit = commitPage; QWizard *wizard = this->wizard(); if (wizard && wizard->currentPage() == this) wizard->d_func()->updateCurrentPage(); } /*! Returns \c true if this page is a commit page; otherwise returns \c false. \sa setCommitPage() */ bool QWizardPage::isCommitPage() const { Q_D(const QWizardPage); return d->commit; } /*! Sets the text on button \a which to be \a text on this page. By default, the text on buttons depends on the QWizard::wizardStyle, but may be redefined for the wizard as a whole using QWizard::setButtonText(). \sa buttonText(), QWizard::setButtonText(), QWizard::buttonText() */ void QWizardPage::setButtonText(QWizard::WizardButton which, const QString &text) { Q_D(QWizardPage); d->buttonCustomTexts.insert(which, text); if (wizard() && wizard()->currentPage() == this && wizard()->d_func()->btns[which]) wizard()->d_func()->btns[which]->setText(text); } /*! Returns the text on button \a which on this page. If a text has ben set using setButtonText(), this text is returned. Otherwise, if a text has been set using QWizard::setButtonText(), this text is returned. By default, the text on buttons depends on the QWizard::wizardStyle. For example, on Mac OS X, the \uicontrol Next button is called \uicontrol Continue. \sa setButtonText(), QWizard::buttonText(), QWizard::setButtonText() */ QString QWizardPage::buttonText(QWizard::WizardButton which) const { Q_D(const QWizardPage); if (d->buttonCustomTexts.contains(which)) return d->buttonCustomTexts.value(which); if (wizard()) return wizard()->buttonText(which); return QString(); } /*! This virtual function is called by QWizard::nextId() to find out which page to show when the user clicks the \uicontrol Next button. The return value is the ID of the next page, or -1 if no page follows. By default, this function returns the lowest ID greater than the ID of the current page, or -1 if there is no such ID. By reimplementing this function, you can specify a dynamic page order. For example: \snippet dialogs/licensewizard/licensewizard.cpp 18 \sa QWizard::nextId() */ int QWizardPage::nextId() const { Q_D(const QWizardPage); if (!d->wizard) return -1; bool foundCurrentPage = false; const QWizardPrivate::PageMap &pageMap = d->wizard->d_func()->pageMap; QWizardPrivate::PageMap::const_iterator i = pageMap.constBegin(); QWizardPrivate::PageMap::const_iterator end = pageMap.constEnd(); for (; i != end; ++i) { if (i.value() == this) { foundCurrentPage = true; } else if (foundCurrentPage) { return i.key(); } } return -1; } /*! \fn void QWizardPage::completeChanged() This signal is emitted whenever the complete state of the page (i.e., the value of isComplete()) changes. If you reimplement isComplete(), make sure to emit completeChanged() whenever the value of isComplete() changes, to ensure that QWizard updates the enabled or disabled state of its buttons. \sa isComplete() */ /*! Sets the value of the field called \a name to \a value. This function can be used to set fields on any page of the wizard. It is equivalent to calling wizard()->\l{QWizard::setField()}{setField(\a name, \a value)}. \sa QWizard::setField(), field(), registerField() */ void QWizardPage::setField(const QString &name, const QVariant &value) { Q_D(QWizardPage); if (!d->wizard) return; d->wizard->setField(name, value); } /*! Returns the value of the field called \a name. This function can be used to access fields on any page of the wizard. It is equivalent to calling wizard()->\l{QWizard::field()}{field(\a name)}. Example: \snippet dialogs/classwizard/classwizard.cpp 17 \sa QWizard::field(), setField(), registerField() */ QVariant QWizardPage::field(const QString &name) const { Q_D(const QWizardPage); if (!d->wizard) return QVariant(); return d->wizard->field(name); } /*! Creates a field called \a name associated with the given \a property of the given \a widget. From then on, that property becomes accessible using field() and setField(). Fields are global to the entire wizard and make it easy for any single page to access information stored by another page, without having to put all the logic in QWizard or having the pages know explicitly about each other. If \a name ends with an asterisk (\c *), the field is a mandatory field. When a page has mandatory fields, the \uicontrol Next and/or \uicontrol Finish buttons are enabled only when all mandatory fields are filled. This requires a \a changedSignal to be specified, to tell QWizard to recheck the value stored by the mandatory field. QWizard knows the most common Qt widgets. For these (or their subclasses), you don't need to specify a \a property or a \a changedSignal. The table below lists these widgets: \table \header \li Widget \li Property \li Change Notification Signal \row \li QAbstractButton \li bool \l{QAbstractButton::}{checked} \li \l{QAbstractButton::}{toggled()} \row \li QAbstractSlider \li int \l{QAbstractSlider::}{value} \li \l{QAbstractSlider::}{valueChanged()} \row \li QComboBox \li int \l{QComboBox::}{currentIndex} \li \l{QComboBox::}{currentIndexChanged()} \row \li QDateTimeEdit \li QDateTime \l{QDateTimeEdit::}{dateTime} \li \l{QDateTimeEdit::}{dateTimeChanged()} \row \li QLineEdit \li QString \l{QLineEdit::}{text} \li \l{QLineEdit::}{textChanged()} \row \li QListWidget \li int \l{QListWidget::}{currentRow} \li \l{QListWidget::}{currentRowChanged()} \row \li QSpinBox \li int \l{QSpinBox::}{value} \li \l{QSpinBox::}{valueChanged()} \endtable You can use QWizard::setDefaultProperty() to add entries to this table or to override existing entries. To consider a field "filled", QWizard simply checks that their current value doesn't equal their original value (the value they had before initializePage() was called). For QLineEdit, it also checks that \l{QLineEdit::hasAcceptableInput()}{hasAcceptableInput()} returns true, to honor any validator or mask. QWizard's mandatory field mechanism is provided for convenience. It can be bypassed by reimplementing QWizardPage::isComplete(). \sa field(), setField(), QWizard::setDefaultProperty() */ void QWizardPage::registerField(const QString &name, QWidget *widget, const char *property, const char *changedSignal) { Q_D(QWizardPage); QWizardField field(this, name, widget, property, changedSignal); if (d->wizard) { d->wizard->d_func()->addField(field); } else { d->pendingFields += field; } } /*! Returns the wizard associated with this page, or 0 if this page hasn't been inserted into a QWizard yet. \sa QWizard::addPage(), QWizard::setPage() */ QWizard *QWizardPage::wizard() const { Q_D(const QWizardPage); return d->wizard; } QT_END_NAMESPACE #include "moc_qwizard.cpp" #endif // QT_NO_WIZARD