diff options
author | Gunnar Sletta <gunnar.sletta@nokia.com> | 2011-09-13 08:54:45 +0200 |
---|---|---|
committer | Gunnar Sletta <gunnar.sletta@nokia.com> | 2011-09-13 08:54:45 +0200 |
commit | b62bd0584a7872b6917917009b707785b3abd077 (patch) | |
tree | 9981f274712c098cabbff0c4667672a3934e5393 /src/widgets/dialogs | |
parent | 5e10745dca1d10025404a9f268f03ae697fb10cc (diff) | |
parent | 97baad65f65783d2b5ff938f6217aec9434f2e5f (diff) |
Merge branch 'refactor'
Conflicts:
mkspecs/qws/linux-lsb-g++/qmake.conf
src/gui/image/qpixmap_mac.cpp
src/gui/painting/qpaintengine_x11.cpp
src/gui/painting/qtessellator.cpp
src/gui/text/qfontengine_qws.cpp
src/gui/text/qfontengine_x11.cpp
src/gui/widgets/qlinecontrol.cpp
src/opengl/qgl.h
src/opengl/qgl_x11egl.cpp
src/plugins/plugins.pro
Change-Id: If52dcd55cd55f2983a756c2f843967702b60a310
Diffstat (limited to 'src/widgets/dialogs')
46 files changed, 29948 insertions, 0 deletions
diff --git a/src/widgets/dialogs/dialogs.pri b/src/widgets/dialogs/dialogs.pri new file mode 100644 index 0000000000..41d81b8afc --- /dev/null +++ b/src/widgets/dialogs/dialogs.pri @@ -0,0 +1,82 @@ +# Qt dialogs module + +HEADERS += \ + dialogs/qcolordialog.h \ + dialogs/qcolordialog_p.h \ + dialogs/qfscompleter_p.h \ + dialogs/qdialog.h \ + dialogs/qdialog_p.h \ + dialogs/qerrormessage.h \ + dialogs/qfiledialog.h \ + dialogs/qfiledialog_p.h \ + dialogs/qfontdialog.h \ + dialogs/qfontdialog_p.h \ + dialogs/qinputdialog.h \ + dialogs/qmessagebox.h \ + dialogs/qprogressdialog.h \ + dialogs/qsidebar_p.h \ + dialogs/qfilesystemmodel.h \ + dialogs/qfilesystemmodel_p.h \ + dialogs/qfileinfogatherer_p.h \ + dialogs/qwizard.h + +!qpa:mac { + OBJECTIVE_SOURCES += dialogs/qfiledialog_mac.mm \ + dialogs/qfontdialog_mac.mm \ + dialogs/qnspanelproxy_mac.mm + +# Compile qcolordialog_mac.mm with exception support, disregarding the -no-exceptions +# configure option. (qcolordialog_mac needs to catch exceptions thrown by cocoa) + EXCEPTION_SOURCES = dialogs/qcolordialog_mac.mm + exceptions_compiler.commands = $$QMAKE_CXX -c + exceptions_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + exceptions_compiler.commands += -fexceptions + exceptions_compiler.dependency_type = TYPE_C + exceptions_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + exceptions_compiler.input = EXCEPTION_SOURCES + exceptions_compiler.variable_out = OBJECTS + exceptions_compiler.name = compiling[exceptopns] ${QMAKE_FILE_IN} + silent:exceptions_compiler.commands = @echo compiling[exceptopns] ${QMAKE_FILE_IN} && $$exceptions_compiler.commands + QMAKE_EXTRA_COMPILERS += exceptions_compiler +} + +win32 { + qpa:DEFINES += QT_NO_PRINTDIALOG + + HEADERS += dialogs/qwizard_win_p.h \ + dialogs/qfiledialog_win_p.h + SOURCES += dialogs/qdialogsbinarycompat_win.cpp \ + dialogs/qfiledialog_win.cpp \ + dialogs/qwizard_win.cpp + + !win32-borland:!wince*: LIBS += -lshell32 # the filedialog needs this library +} + +wince*|symbian: FORMS += dialogs/qfiledialog_embedded.ui +else: FORMS += dialogs/qfiledialog.ui + +INCLUDEPATH += $$PWD +SOURCES += \ + dialogs/qcolordialog.cpp \ + dialogs/qdialog.cpp \ + dialogs/qerrormessage.cpp \ + dialogs/qfiledialog.cpp \ + dialogs/qfontdialog.cpp \ + dialogs/qinputdialog.cpp \ + dialogs/qmessagebox.cpp \ + dialogs/qprogressdialog.cpp \ + dialogs/qsidebar.cpp \ + dialogs/qfilesystemmodel.cpp \ + dialogs/qfileinfogatherer.cpp \ + dialogs/qwizard.cpp \ + +symbian:contains(QT_CONFIG, s60) { + LIBS += -lCommonDialogs + SOURCES += dialogs/qfiledialog_symbian.cpp \ + dialogs/qcolordialog_symbian.cpp +} + +RESOURCES += dialogs/qmessagebox.qrc + +# Compensate for lack of platform defines in Symbian3 +symbian: DEFINES += SYMBIAN_VERSION_$$upper($$replace(SYMBIAN_VERSION,\\.,_)) diff --git a/src/widgets/dialogs/images/qtlogo-64.png b/src/widgets/dialogs/images/qtlogo-64.png Binary files differnew file mode 100644 index 0000000000..4f68e162de --- /dev/null +++ b/src/widgets/dialogs/images/qtlogo-64.png diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp new file mode 100644 index 0000000000..b26e911899 --- /dev/null +++ b/src/widgets/dialogs/qcolordialog.cpp @@ -0,0 +1,2115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" + +#ifndef QT_NO_COLORDIALOG + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qimage.h" +#include "qdrag.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qlineedit.h" +#include "qmenu.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qpushbutton.h" +#include "qsettings.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qvalidator.h" +#include "qmime.h" +#include "qspinbox.h" +#include "qdialogbuttonbox.h" +#include "private/qguiplatformplugin_p.h" + +#ifdef Q_WS_S60 +#include "private/qt_s60_p.h" +#endif + +#if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5) +# define QT_SMALL_COLORDIALOG +#endif + +QT_BEGIN_NAMESPACE + +//////////// QWellArray BEGIN + +struct QWellArrayData; + +class QWellArray : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int selectedColumn READ selectedColumn) + Q_PROPERTY(int selectedRow READ selectedRow) + +public: + QWellArray(int rows, int cols, QWidget* parent=0); + ~QWellArray() {} + QString cellContent(int row, int col) const; + + int selectedColumn() const { return selCol; } + int selectedRow() const { return selRow; } + + virtual void setCurrent(int row, int col); + virtual void setSelected(int row, int col); + + QSize sizeHint() const; + + virtual void setCellBrush(int row, int col, const QBrush &); + QBrush cellBrush(int row, int col); + + inline int cellWidth() const + { return cellw; } + + inline int cellHeight() const + { return cellh; } + + inline int rowAt(int y) const + { return y / cellh; } + + inline int columnAt(int x) const + { if (isRightToLeft()) return ncols - (x / cellw) - 1; return x / cellw; } + + inline int rowY(int row) const + { return cellh * row; } + + inline int columnX(int column) const + { if (isRightToLeft()) return cellw * (ncols - column - 1); return cellw * column; } + + inline int numRows() const + { return nrows; } + + inline int numCols() const + {return ncols; } + + inline QRect cellRect() const + { return QRect(0, 0, cellw, cellh); } + + inline QSize gridSize() const + { return QSize(ncols * cellw, nrows * cellh); } + + QRect cellGeometry(int row, int column) + { + QRect r; + if (row >= 0 && row < nrows && column >= 0 && column < ncols) + r.setRect(columnX(column), rowY(row), cellw, cellh); + return r; + } + + inline void updateCell(int row, int column) { update(cellGeometry(row, column)); } + +signals: + void selected(int row, int col); + +protected: + virtual void paintCell(QPainter *, int row, int col, const QRect&); + virtual void paintCellContents(QPainter *, int row, int col, const QRect&); + + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void keyPressEvent(QKeyEvent*); + void focusInEvent(QFocusEvent*); + void focusOutEvent(QFocusEvent*); + void paintEvent(QPaintEvent *); + +private: + Q_DISABLE_COPY(QWellArray) + + int nrows; + int ncols; + int cellw; + int cellh; + int curRow; + int curCol; + int selRow; + int selCol; + QWellArrayData *d; +}; + +void QWellArray::paintEvent(QPaintEvent *e) +{ + QRect r = e->rect(); + int cx = r.x(); + int cy = r.y(); + int ch = r.height(); + int cw = r.width(); + int colfirst = columnAt(cx); + int collast = columnAt(cx + cw); + int rowfirst = rowAt(cy); + int rowlast = rowAt(cy + ch); + + if (isRightToLeft()) { + int t = colfirst; + colfirst = collast; + collast = t; + } + + QPainter painter(this); + QPainter *p = &painter; + QRect rect(0, 0, cellWidth(), cellHeight()); + + + if (collast < 0 || collast >= ncols) + collast = ncols-1; + if (rowlast < 0 || rowlast >= nrows) + rowlast = nrows-1; + + // Go through the rows + for (int r = rowfirst; r <= rowlast; ++r) { + // get row position and height + int rowp = rowY(r); + + // Go through the columns in the row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + for (int c = colfirst; c <= collast; ++c) { + // get position and width of column c + int colp = columnX(c); + // Translate painter and draw the cell + rect.translate(colp, rowp); + paintCell(p, r, c, rect); + rect.translate(-colp, -rowp); + } + } +} + +struct QWellArrayData { + QBrush *brush; +}; + +QWellArray::QWellArray(int rows, int cols, QWidget *parent) + : QWidget(parent) + ,nrows(rows), ncols(cols) +{ + d = 0; + setFocusPolicy(Qt::StrongFocus); + cellw = 28; + cellh = 24; + curCol = 0; + curRow = 0; + selCol = -1; + selRow = -1; +} + +QSize QWellArray::sizeHint() const +{ + ensurePolished(); + return gridSize().boundedTo(QSize(640, 480)); +} + + +void QWellArray::paintCell(QPainter* p, int row, int col, const QRect &rect) +{ + int b = 3; //margin + + const QPalette & g = palette(); + QStyleOptionFrame opt; + int dfw = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + opt.lineWidth = dfw; + opt.midLineWidth = 1; + opt.rect = rect.adjusted(b, b, -b, -b); + opt.palette = g; + opt.state = QStyle::State_Enabled | QStyle::State_Sunken; + style()->drawPrimitive(QStyle::PE_Frame, &opt, p, this); + b += dfw; + + if ((row == curRow) && (col == curCol)) { + if (hasFocus()) { + QStyleOptionFocusRect opt; + opt.palette = g; + opt.rect = rect; + opt.state = QStyle::State_None | QStyle::State_KeyboardFocusChange; + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, p, this); + } + } + paintCellContents(p, row, col, opt.rect.adjusted(dfw, dfw, -dfw, -dfw)); +} + +/*! + Reimplement this function to change the contents of the well array. + */ +void QWellArray::paintCellContents(QPainter *p, int row, int col, const QRect &r) +{ + if (d) { + p->fillRect(r, d->brush[row*numCols()+col]); + } else { + p->fillRect(r, Qt::white); + p->setPen(Qt::black); + p->drawLine(r.topLeft(), r.bottomRight()); + p->drawLine(r.topRight(), r.bottomLeft()); + } +} + +void QWellArray::mousePressEvent(QMouseEvent *e) +{ + // The current cell marker is set to the cell the mouse is pressed in + QPoint pos = e->pos(); + setCurrent(rowAt(pos.y()), columnAt(pos.x())); +} + +void QWellArray::mouseReleaseEvent(QMouseEvent * /* event */) +{ + // The current cell marker is set to the cell the mouse is clicked in + setSelected(curRow, curCol); +} + + +/* + Sets the cell currently having the focus. This is not necessarily + the same as the currently selected cell. +*/ + +void QWellArray::setCurrent(int row, int col) +{ + if ((curRow == row) && (curCol == col)) + return; + + if (row < 0 || col < 0) + row = col = -1; + + int oldRow = curRow; + int oldCol = curCol; + + curRow = row; + curCol = col; + + updateCell(oldRow, oldCol); + updateCell(curRow, curCol); +} + +/* + Sets the currently selected cell to \a row, \a column. If \a row or + \a column are less than zero, the current cell is unselected. + + Does not set the position of the focus indicator. +*/ +void QWellArray::setSelected(int row, int col) +{ + int oldRow = selRow; + int oldCol = selCol; + + if (row < 0 || col < 0) + row = col = -1; + + selCol = col; + selRow = row; + + updateCell(oldRow, oldCol); + updateCell(selRow, selCol); + if (row >= 0) + emit selected(row, col); + +#ifndef QT_NO_MENU + if (isVisible() && qobject_cast<QMenu*>(parentWidget())) + parentWidget()->close(); +#endif +} + +void QWellArray::focusInEvent(QFocusEvent*) +{ + updateCell(curRow, curCol); +} + +void QWellArray::setCellBrush(int row, int col, const QBrush &b) +{ + if (!d) { + d = new QWellArrayData; + int i = numRows()*numCols(); + d->brush = new QBrush[i]; + } + if (row >= 0 && row < numRows() && col >= 0 && col < numCols()) + d->brush[row*numCols()+col] = b; +} + +/* + Returns the brush set for the cell at \a row, \a column. If no brush is + set, Qt::NoBrush is returned. +*/ + +QBrush QWellArray::cellBrush(int row, int col) +{ + if (d && row >= 0 && row < numRows() && col >= 0 && col < numCols()) + return d->brush[row*numCols()+col]; + return Qt::NoBrush; +} + + + +/*!\reimp +*/ + +void QWellArray::focusOutEvent(QFocusEvent*) +{ + updateCell(curRow, curCol); +} + +/*\reimp +*/ +void QWellArray::keyPressEvent(QKeyEvent* e) +{ + switch(e->key()) { // Look at the key code + case Qt::Key_Left: // If 'left arrow'-key, + if(curCol > 0) // and cr't not in leftmost col + setCurrent(curRow, curCol - 1); // set cr't to next left column + break; + case Qt::Key_Right: // Correspondingly... + if(curCol < numCols()-1) + setCurrent(curRow, curCol + 1); + break; + case Qt::Key_Up: + if(curRow > 0) + setCurrent(curRow - 1, curCol); + break; + case Qt::Key_Down: + if(curRow < numRows()-1) + setCurrent(curRow + 1, curCol); + break; +#if 0 + // bad idea that shouldn't have been implemented; very counterintuitive + case Qt::Key_Return: + case Qt::Key_Enter: + /* + ignore the key, so that the dialog get it, but still select + the current row/col + */ + e->ignore(); + // fallthrough intended +#endif + case Qt::Key_Space: + setSelected(curRow, curCol); + break; + default: // If not an interesting key, + e->ignore(); // we don't accept the event + return; + } + +} + +//////////// QWellArray END + +static bool initrgb = false; +static QRgb stdrgb[6*8]; +static QRgb cusrgb[2*8]; +static bool customSet = false; + + +static void initRGB() +{ + if (initrgb) + return; + initrgb = true; + int i = 0; + for (int g = 0; g < 4; g++) + for (int r = 0; r < 4; r++) + for (int b = 0; b < 3; b++) + stdrgb[i++] = qRgb(r * 255 / 3, g * 255 / 3, b * 255 / 2); + + for (i = 0; i < 2*8; i++) + cusrgb[i] = 0xffffffff; +} + +/*! + Returns the number of custom colors supported by QColorDialog. All + color dialogs share the same custom colors. +*/ +int QColorDialog::customCount() +{ + return 2 * 8; +} + +/*! + \since 4.5 + + Returns the custom color at the given \a index as a QRgb value. +*/ +QRgb QColorDialog::customColor(int index) +{ + if (uint(index) >= uint(customCount())) + return qRgb(255, 255, 255); + initRGB(); + return cusrgb[index]; +} + +/*! + Sets the custom color at \a index to the QRgb \a color value. + + \note This function does not apply to the Native Color Dialog on the Mac + OS X platform. If you still require this function, use the + QColorDialog::DontUseNativeDialog option. +*/ +void QColorDialog::setCustomColor(int index, QRgb color) +{ + if (uint(index) >= uint(customCount())) + return; + initRGB(); + customSet = true; + cusrgb[index] = color; +} + +/*! + Sets the standard color at \a index to the QRgb \a color value. + + \note This function does not apply to the Native Color Dialog on the Mac + OS X platform. If you still require this function, use the + QColorDialog::DontUseNativeDialog option. +*/ + +void QColorDialog::setStandardColor(int index, QRgb color) +{ + if (uint(index) >= uint(6 * 8)) + return; + initRGB(); + stdrgb[index] = color; +} + +static inline void rgb2hsv(QRgb rgb, int &h, int &s, int &v) +{ + QColor c; + c.setRgb(rgb); + c.getHsv(&h, &s, &v); +} + +class QColorWell : public QWellArray +{ +public: + QColorWell(QWidget *parent, int r, int c, QRgb *vals) + :QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1) + { setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); } + +protected: + void paintCellContents(QPainter *, int row, int col, const QRect&); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dragMoveEvent(QDragMoveEvent *e); + void dropEvent(QDropEvent *e); +#endif + +private: + QRgb *values; + bool mousePressed; + QPoint pressPos; + QPoint oldCurrent; + +}; + +void QColorWell::paintCellContents(QPainter *p, int row, int col, const QRect &r) +{ + int i = row + col*numRows(); + p->fillRect(r, QColor(values[i])); +} + +void QColorWell::mousePressEvent(QMouseEvent *e) +{ + oldCurrent = QPoint(selectedRow(), selectedColumn()); + QWellArray::mousePressEvent(e); + mousePressed = true; + pressPos = e->pos(); +} + +void QColorWell::mouseMoveEvent(QMouseEvent *e) +{ + QWellArray::mouseMoveEvent(e); +#ifndef QT_NO_DRAGANDDROP + if (!mousePressed) + return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { + setCurrent(oldCurrent.x(), oldCurrent.y()); + int i = rowAt(pressPos.y()) + columnAt(pressPos.x()) * numRows(); + QColor col(values[i]); + QMimeData *mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(cellWidth(), cellHeight()); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->start(); + } +#endif +} + +#ifndef QT_NO_DRAGANDDROP +void QColorWell::dragEnterEvent(QDragEnterEvent *e) +{ + if (qvariant_cast<QColor>(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void QColorWell::dragLeaveEvent(QDragLeaveEvent *) +{ + if (hasFocus()) + parentWidget()->setFocus(); +} + +void QColorWell::dragMoveEvent(QDragMoveEvent *e) +{ + if (qvariant_cast<QColor>(e->mimeData()->colorData()).isValid()) { + setCurrent(rowAt(e->pos().y()), columnAt(e->pos().x())); + e->accept(); + } else { + e->ignore(); + } +} + +void QColorWell::dropEvent(QDropEvent *e) +{ + QColor col = qvariant_cast<QColor>(e->mimeData()->colorData()); + if (col.isValid()) { + int i = rowAt(e->pos().y()) + columnAt(e->pos().x()) * numRows(); + values[i] = col.rgb(); + update(); + e->accept(); + } else { + e->ignore(); + } +} + +#endif // QT_NO_DRAGANDDROP + +void QColorWell::mouseReleaseEvent(QMouseEvent *e) +{ + if (!mousePressed) + return; + QWellArray::mouseReleaseEvent(e); + mousePressed = false; +} + +class QColorPicker : public QFrame +{ + Q_OBJECT +public: + QColorPicker(QWidget* parent); + ~QColorPicker(); + +public slots: + void setCol(int h, int s); + +signals: + void newCol(int h, int s); + +protected: + QSize sizeHint() const; + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void resizeEvent(QResizeEvent *); + +private: + int hue; + int sat; + + QPoint colPt(); + int huePt(const QPoint &pt); + int satPt(const QPoint &pt); + void setCol(const QPoint &pt); + + QPixmap pix; +}; + +static int pWidth = 220; +static int pHeight = 200; + +class QColorLuminancePicker : public QWidget +{ + Q_OBJECT +public: + QColorLuminancePicker(QWidget* parent=0); + ~QColorLuminancePicker(); + +public slots: + void setCol(int h, int s, int v); + void setCol(int h, int s); + +signals: + void newHsv(int h, int s, int v); + +protected: + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + +private: + enum { foff = 3, coff = 4 }; //frame and contents offset + int val; + int hue; + int sat; + + int y2val(int y); + int val2y(int val); + void setVal(int v); + + QPixmap *pix; +}; + + +int QColorLuminancePicker::y2val(int y) +{ + int d = height() - 2*coff - 1; + return 255 - (y - coff)*255/d; +} + +int QColorLuminancePicker::val2y(int v) +{ + int d = height() - 2*coff - 1; + return coff + (255-v)*d/255; +} + +QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) + :QWidget(parent) +{ + hue = 100; val = 100; sat = 100; + pix = 0; + // setAttribute(WA_NoErase, true); +} + +QColorLuminancePicker::~QColorLuminancePicker() +{ + delete pix; +} + +void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) +{ + setVal(y2val(m->y())); +} +void QColorLuminancePicker::mousePressEvent(QMouseEvent *m) +{ + setVal(y2val(m->y())); +} + +void QColorLuminancePicker::setVal(int v) +{ + if (val == v) + return; + val = qMax(0, qMin(v,255)); + delete pix; pix=0; + repaint(); + emit newHsv(hue, sat, val); +} + +//receives from a hue,sat chooser and relays. +void QColorLuminancePicker::setCol(int h, int s) +{ + setCol(h, s, val); + emit newHsv(h, s, val); +} + +void QColorLuminancePicker::paintEvent(QPaintEvent *) +{ + int w = width() - 5; + + QRect r(0, foff, w, height() - 2*foff); + int wi = r.width() - 2; + int hi = r.height() - 2; + if (!pix || pix->height() != hi || pix->width() != wi) { + delete pix; + QImage img(wi, hi, QImage::Format_RGB32); + int y; + uint *pixel = (uint *) img.scanLine(0); + for (y = 0; y < hi; y++) { + const uint *end = pixel + wi; + while (pixel < end) { + QColor c; + c.setHsv(hue, sat, y2val(y+coff)); + *pixel = c.rgb(); + ++pixel; + } + } + pix = new QPixmap(QPixmap::fromImage(img)); + } + QPainter p(this); + p.drawPixmap(1, coff, *pix); + const QPalette &g = palette(); + qDrawShadePanel(&p, r, g, true); + p.setPen(g.foreground().color()); + p.setBrush(g.foreground()); + QPolygon a; + int y = val2y(val); + a.setPoints(3, w, y, w+5, y+5, w+5, y-5); + p.eraseRect(w, 0, 5, height()); + p.drawPolygon(a); +} + +void QColorLuminancePicker::setCol(int h, int s , int v) +{ + val = v; + hue = h; + sat = s; + delete pix; pix=0; + repaint(); +} + +QPoint QColorPicker::colPt() +{ + QRect r = contentsRect(); + return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255); +} + +int QColorPicker::huePt(const QPoint &pt) +{ + QRect r = contentsRect(); + return 360 - pt.x() * 360 / (r.width() - 1); +} + +int QColorPicker::satPt(const QPoint &pt) +{ + QRect r = contentsRect(); + return 255 - pt.y() * 255 / (r.height() - 1); +} + +void QColorPicker::setCol(const QPoint &pt) +{ + setCol(huePt(pt), satPt(pt)); +} + +QColorPicker::QColorPicker(QWidget* parent) + : QFrame(parent) +{ + hue = 0; sat = 0; + setCol(150, 255); + + setAttribute(Qt::WA_NoSystemBackground); + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) ); +} + +QColorPicker::~QColorPicker() +{ +} + +QSize QColorPicker::sizeHint() const +{ + return QSize(pWidth + 2*frameWidth(), pHeight + 2*frameWidth()); +} + +void QColorPicker::setCol(int h, int s) +{ + int nhue = qMin(qMax(0,h), 359); + int nsat = qMin(qMax(0,s), 255); + if (nhue == hue && nsat == sat) + return; + + QRect r(colPt(), QSize(20,20)); + hue = nhue; sat = nsat; + r = r.united(QRect(colPt(), QSize(20,20))); + r.translate(contentsRect().x()-9, contentsRect().y()-9); + // update(r); + repaint(r); +} + +void QColorPicker::mouseMoveEvent(QMouseEvent *m) +{ + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); +} + +void QColorPicker::mousePressEvent(QMouseEvent *m) +{ + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); +} + +void QColorPicker::paintEvent(QPaintEvent* ) +{ + QPainter p(this); + drawFrame(&p); + QRect r = contentsRect(); + + p.drawPixmap(r.topLeft(), pix); + QPoint pt = colPt() + r.topLeft(); + p.setPen(Qt::black); + + p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black); + p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black); + +} + +void QColorPicker::resizeEvent(QResizeEvent *ev) +{ + QFrame::resizeEvent(ev); + + int w = width() - frameWidth() * 2; + int h = height() - frameWidth() * 2; + QImage img(w, h, QImage::Format_RGB32); + int x, y; + uint *pixel = (uint *) img.scanLine(0); + for (y = 0; y < h; y++) { + const uint *end = pixel + w; + x = 0; + while (pixel < end) { + QPoint p(x, y); + QColor c; + c.setHsv(huePt(p), satPt(p), 200); + *pixel = c.rgb(); + ++pixel; + ++x; + } + } + pix = QPixmap::fromImage(img); +} + + +class QColSpinBox : public QSpinBox +{ +public: + QColSpinBox(QWidget *parent) + : QSpinBox(parent) { setRange(0, 255); } + void setValue(int i) { + bool block = signalsBlocked(); + blockSignals(true); + QSpinBox::setValue(i); + blockSignals(block); + } +}; + +class QColorShowLabel; + +class QColorShower : public QWidget +{ + Q_OBJECT +public: + QColorShower(QColorDialog *parent); + + //things that don't emit signals + void setHsv(int h, int s, int v); + + int currentAlpha() const + { return (colorDialog->options() & QColorDialog::ShowAlphaChannel) ? alphaEd->value() : 255; } + void setCurrentAlpha(int a) { alphaEd->setValue(a); rgbEd(); } + void showAlpha(bool b); + bool isAlphaVisible() const; + + QRgb currentColor() const { return curCol; } + QColor currentQColor() const { return curQColor; } + void retranslateStrings(); + void updateQColor(); + +public slots: + void setRgb(QRgb rgb); + +signals: + void newCol(QRgb rgb); + void currentColorChanged(const QColor &color); + +private slots: + void rgbEd(); + void hsvEd(); +private: + void showCurrentColor(); + int hue, sat, val; + QRgb curCol; + QColor curQColor; + QLabel *lblHue; + QLabel *lblSat; + QLabel *lblVal; + QLabel *lblRed; + QLabel *lblGreen; + QLabel *lblBlue; + QColSpinBox *hEd; + QColSpinBox *sEd; + QColSpinBox *vEd; + QColSpinBox *rEd; + QColSpinBox *gEd; + QColSpinBox *bEd; + QColSpinBox *alphaEd; + QLabel *alphaLab; + QColorShowLabel *lab; + bool rgbOriginal; + QColorDialog *colorDialog; + + friend class QColorDialog; + friend class QColorDialogPrivate; +}; + +class QColorShowLabel : public QFrame +{ + Q_OBJECT + +public: + QColorShowLabel(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::Panel|QFrame::Sunken); + setAcceptDrops(true); + mousePressed = false; + } + void setColor(QColor c) { col = c; } + +signals: + void colorDropped(QRgb); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *e); +#endif + +private: + QColor col; + bool mousePressed; + QPoint pressPos; +}; + +void QColorShowLabel::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + drawFrame(&p); + p.fillRect(contentsRect()&e->rect(), col); +} + +void QColorShower::showAlpha(bool b) +{ + alphaLab->setVisible(b); + alphaEd->setVisible(b); +} + +inline bool QColorShower::isAlphaVisible() const +{ + return alphaLab->isVisible(); +} + +void QColorShowLabel::mousePressEvent(QMouseEvent *e) +{ + mousePressed = true; + pressPos = e->pos(); +} + +void QColorShowLabel::mouseMoveEvent(QMouseEvent *e) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(e); +#else + if (!mousePressed) + return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { + QMimeData *mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(30, 20); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->start(); + } +#endif +} + +#ifndef QT_NO_DRAGANDDROP +void QColorShowLabel::dragEnterEvent(QDragEnterEvent *e) +{ + if (qvariant_cast<QColor>(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void QColorShowLabel::dragLeaveEvent(QDragLeaveEvent *) +{ +} + +void QColorShowLabel::dropEvent(QDropEvent *e) +{ + QColor color = qvariant_cast<QColor>(e->mimeData()->colorData()); + if (color.isValid()) { + col = color; + repaint(); + emit colorDropped(col.rgb()); + e->accept(); + } else { + e->ignore(); + } +} +#endif // QT_NO_DRAGANDDROP + +void QColorShowLabel::mouseReleaseEvent(QMouseEvent *) +{ + if (!mousePressed) + return; + mousePressed = false; +} + +QColorShower::QColorShower(QColorDialog *parent) + : QWidget(parent) +{ + colorDialog = parent; + + curCol = qRgb(255, 255, 255); + curQColor = Qt::white; + + QGridLayout *gl = new QGridLayout(this); + gl->setMargin(gl->spacing()); + lab = new QColorShowLabel(this); + +#ifdef QT_SMALL_COLORDIALOG +# ifdef Q_WS_S60 + const bool nonTouchUI = !S60->hasTouchscreen; +# elif defined Q_WS_MAEMO_5 + const bool nonTouchUI = false; +# endif +#endif + +#ifndef Q_WS_WINCE +#ifdef QT_SMALL_COLORDIALOG + lab->setMinimumHeight(60); +#endif + lab->setMinimumWidth(60); +#else + lab->setMinimumWidth(20); +#endif + +// In S60, due to small screen and different screen layouts need to re-arrange the widgets. +// For QVGA screens only the comboboxes and color label are visible. +// For nHD screens only color and luminence pickers and color label are visible. +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lab, 0, 0, -1, 1); +#else + if (nonTouchUI) + gl->addWidget(lab, 0, 0, 1, -1); + else + gl->addWidget(lab, 0, 0, -1, 1); +#endif + connect(lab, SIGNAL(colorDropped(QRgb)), this, SIGNAL(newCol(QRgb))); + connect(lab, SIGNAL(colorDropped(QRgb)), this, SLOT(setRgb(QRgb))); + + hEd = new QColSpinBox(this); + hEd->setRange(0, 359); + lblHue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblHue->setBuddy(hEd); +#endif + lblHue->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblHue, 0, 1); + gl->addWidget(hEd, 0, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblHue, 1, 0); + gl->addWidget(hEd, 2, 0); + } else { + lblHue->hide(); + hEd->hide(); + } +#endif + + sEd = new QColSpinBox(this); + lblSat = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblSat->setBuddy(sEd); +#endif + lblSat->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 1, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 2, 1); + } else { + lblSat->hide(); + sEd->hide(); + } +#endif + + vEd = new QColSpinBox(this); + lblVal = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblVal->setBuddy(vEd); +#endif + lblVal->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblVal, 2, 1); + gl->addWidget(vEd, 2, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblVal, 1, 2); + gl->addWidget(vEd, 2, 2); + } else { + lblVal->hide(); + vEd->hide(); + } +#endif + + rEd = new QColSpinBox(this); + lblRed = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblRed->setBuddy(rEd); +#endif + lblRed->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblRed, 0, 3); + gl->addWidget(rEd, 0, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblRed, 3, 0); + gl->addWidget(rEd, 4, 0); + } else { + lblRed->hide(); + rEd->hide(); + } +#endif + + gEd = new QColSpinBox(this); + lblGreen = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblGreen->setBuddy(gEd); +#endif + lblGreen->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblGreen, 1, 3); + gl->addWidget(gEd, 1, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblGreen, 3, 1); + gl->addWidget(gEd, 4, 1); + } else { + lblGreen->hide(); + gEd->hide(); + } +#endif + + bEd = new QColSpinBox(this); + lblBlue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblBlue->setBuddy(bEd); +#endif + lblBlue->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblBlue, 2, 3); + gl->addWidget(bEd, 2, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblBlue, 3, 2); + gl->addWidget(bEd, 4, 2); + } else { + lblBlue->hide(); + bEd->hide(); + } +#endif + + alphaEd = new QColSpinBox(this); + alphaLab = new QLabel(this); +#ifndef QT_NO_SHORTCUT + alphaLab->setBuddy(alphaEd); +#endif + alphaLab->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(alphaLab, 3, 1, 1, 3); + gl->addWidget(alphaEd, 3, 4); +#else + if (nonTouchUI) { + gl->addWidget(alphaLab, 1, 3, 3, 1); + gl->addWidget(alphaEd, 4, 3); + } else { + alphaLab->hide(); + alphaEd->hide(); + } +#endif + alphaEd->hide(); + alphaLab->hide(); + + connect(hEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(sEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(vEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + + connect(rEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(gEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(bEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(alphaEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + + retranslateStrings(); +} + +inline QRgb QColorDialogPrivate::currentColor() const { return cs->currentColor(); } +inline int QColorDialogPrivate::currentAlpha() const { return cs->currentAlpha(); } +inline void QColorDialogPrivate::setCurrentAlpha(int a) { cs->setCurrentAlpha(a); } +inline void QColorDialogPrivate::showAlpha(bool b) { cs->showAlpha(b); } +inline bool QColorDialogPrivate::isAlphaVisible() const { return cs->isAlphaVisible(); } + +QColor QColorDialogPrivate::currentQColor() const +{ + return cs->currentQColor(); +} + +void QColorShower::showCurrentColor() +{ + lab->setColor(currentColor()); + lab->repaint(); +} + +void QColorShower::rgbEd() +{ + rgbOriginal = true; + curCol = qRgba(rEd->value(), gEd->value(), bEd->value(), currentAlpha()); + + rgb2hsv(currentColor(), hue, sat, val); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); +} + +void QColorShower::hsvEd() +{ + rgbOriginal = false; + hue = hEd->value(); + sat = sEd->value(); + val = vEd->value(); + + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); +} + +void QColorShower::setRgb(QRgb rgb) +{ + rgbOriginal = true; + curCol = rgb; + + rgb2hsv(currentColor(), hue, sat, val); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + updateQColor(); +} + +void QColorShower::setHsv(int h, int s, int v) +{ + if (h < -1 || (uint)s > 255 || (uint)v > 255) + return; + + rgbOriginal = false; + hue = h; val = v; sat = s; + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + updateQColor(); +} + +void QColorShower::retranslateStrings() +{ + lblHue->setText(QColorDialog::tr("Hu&e:")); + lblSat->setText(QColorDialog::tr("&Sat:")); + lblVal->setText(QColorDialog::tr("&Val:")); + lblRed->setText(QColorDialog::tr("&Red:")); + lblGreen->setText(QColorDialog::tr("&Green:")); + lblBlue->setText(QColorDialog::tr("Bl&ue:")); + alphaLab->setText(QColorDialog::tr("A&lpha channel:")); +} + +void QColorShower::updateQColor() +{ + QColor oldQColor(curQColor); + curQColor.setRgba(qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), currentAlpha())); + if (curQColor != oldQColor) + emit currentColorChanged(curQColor); +} + +//sets all widgets to display h,s,v +void QColorDialogPrivate::_q_newHsv(int h, int s, int v) +{ + cs->setHsv(h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); +} + +//sets all widgets to display rgb +void QColorDialogPrivate::setCurrentColor(QRgb rgb) +{ + cs->setRgb(rgb); + _q_newColorTypedIn(rgb); +} + +// hack; doesn't keep curCol in sync, so use with care +void QColorDialogPrivate::setCurrentQColor(const QColor &color) +{ + Q_Q(QColorDialog); + if (cs->curQColor != color) { + cs->curQColor = color; + emit q->currentColorChanged(color); + } +} + +bool QColorDialogPrivate::selectColor(const QColor &col) +{ + QRgb color = col.rgb(); + int i = 0, j = 0; + // Check standard colors + if (standard) { + for (i = 0; i < 6; i++) { + for (j = 0; j < 8; j++) { + if (color == stdrgb[i + j*6]) { + _q_newStandard(i, j); + standard->setCurrent(i, j); + standard->setSelected(i, j); + standard->setFocus(); + return true; + } + } + } + } + // Check custom colors + if (custom) { + for (i = 0; i < 2; i++) { + for (j = 0; j < 8; j++) { + if (color == cusrgb[i + j*2]) { + _q_newCustom(i, j); + custom->setCurrent(i, j); + custom->setSelected(i, j); + custom->setFocus(); + return true; + } + } + } + } + return false; +} + +//sets all widgets except cs to display rgb +void QColorDialogPrivate::_q_newColorTypedIn(QRgb rgb) +{ + int h, s, v; + rgb2hsv(rgb, h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); +} + +void QColorDialogPrivate::_q_newCustom(int r, int c) +{ + int i = r+2*c; + setCurrentColor(cusrgb[i]); + nextCust = i; + if (standard) + standard->setSelected(-1,-1); +} + +void QColorDialogPrivate::_q_newStandard(int r, int c) +{ + setCurrentColor(stdrgb[r+c*6]); + if (custom) + custom->setSelected(-1,-1); +} + +void QColorDialogPrivate::init(const QColor &initial) +{ + Q_Q(QColorDialog); + + q->setSizeGripEnabled(false); + q->setWindowTitle(QColorDialog::tr("Select Color")); + + nativeDialogInUse = false; + + nextCust = 0; + QVBoxLayout *mainLay = new QVBoxLayout(q); + // there's nothing in this dialog that benefits from sizing up + mainLay->setSizeConstraint(QLayout::SetFixedSize); + + QHBoxLayout *topLay = new QHBoxLayout(); + mainLay->addLayout(topLay); + + leftLay = 0; + +#if defined(Q_WS_WINCE) || defined(QT_SMALL_COLORDIALOG) + smallDisplay = true; + const int lumSpace = 20; +#else + // small displays (e.g. PDAs) cannot fit the full color dialog, + // so just use the color picker. + smallDisplay = (QApplication::desktop()->width() < 480 || QApplication::desktop()->height() < 350); + const int lumSpace = topLay->spacing() / 2; +#endif + + if (!smallDisplay) { + leftLay = new QVBoxLayout; + topLay->addLayout(leftLay); + } + + initRGB(); + +#ifndef QT_NO_SETTINGS + if (!customSet) { + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + for (int i = 0; i < 2*8; ++i) { + QVariant v = settings.value(QLatin1String("Qt/customColors/") + QString::number(i)); + if (v.isValid()) { + QRgb rgb = v.toUInt(); + cusrgb[i] = rgb; + } + } + } +#endif + +#if defined(QT_SMALL_COLORDIALOG) +# if defined(Q_WS_S60) + const bool nonTouchUI = !S60->hasTouchscreen; +# elif defined(Q_WS_MAEMO_5) + const bool nonTouchUI = false; +# endif +#endif + + if (!smallDisplay) { + standard = new QColorWell(q, 6, 8, stdrgb); + lblBasicColors = new QLabel(q); +#ifndef QT_NO_SHORTCUT + lblBasicColors->setBuddy(standard); +#endif + q->connect(standard, SIGNAL(selected(int,int)), SLOT(_q_newStandard(int,int))); + leftLay->addWidget(lblBasicColors); + leftLay->addWidget(standard); + +#if !defined(Q_WS_WINCE) + leftLay->addStretch(); +#endif + + custom = new QColorWell(q, 2, 8, cusrgb); + custom->setAcceptDrops(true); + + q->connect(custom, SIGNAL(selected(int,int)), SLOT(_q_newCustom(int,int))); + lblCustomColors = new QLabel(q); +#ifndef QT_NO_SHORTCUT + lblCustomColors->setBuddy(custom); +#endif + leftLay->addWidget(lblCustomColors); + leftLay->addWidget(custom); + + addCusBt = new QPushButton(q); + QObject::connect(addCusBt, SIGNAL(clicked()), q, SLOT(_q_addCustom())); + leftLay->addWidget(addCusBt); + } else { + // better color picker size for small displays +#if defined(QT_SMALL_COLORDIALOG) + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); + pWidth = pHeight = qMin(screenSize.width(), screenSize.height()); + pHeight -= 20; + if(screenSize.height() > screenSize.width()) + pWidth -= 20; +#else + pWidth = 150; + pHeight = 100; +#endif + custom = 0; + standard = 0; + } + + QVBoxLayout *rightLay = new QVBoxLayout; + topLay->addLayout(rightLay); + + QHBoxLayout *pickLay = new QHBoxLayout; + rightLay->addLayout(pickLay); + + QVBoxLayout *cLay = new QVBoxLayout; + pickLay->addLayout(cLay); + cp = new QColorPicker(q); + + cp->setFrameStyle(QFrame::Panel + QFrame::Sunken); + +#if defined(QT_SMALL_COLORDIALOG) + if (!nonTouchUI) { + pickLay->addWidget(cp); + cLay->addSpacing(lumSpace); + } else { + cp->hide(); + } +#else + cLay->addSpacing(lumSpace); + cLay->addWidget(cp); +#endif + cLay->addSpacing(lumSpace); + + lp = new QColorLuminancePicker(q); +#if defined(QT_SMALL_COLORDIALOG) + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); + const int minDimension = qMin(screenSize.height(), screenSize.width()); + //set picker to be finger-usable + int pickerWidth = !nonTouchUI ? minDimension/9 : minDimension/12; + lp->setFixedWidth(pickerWidth); + if (!nonTouchUI) + pickLay->addWidget(lp); + else + lp->hide(); +#else + lp->setFixedWidth(20); + pickLay->addWidget(lp); +#endif + + QObject::connect(cp, SIGNAL(newCol(int,int)), lp, SLOT(setCol(int,int))); + QObject::connect(lp, SIGNAL(newHsv(int,int,int)), q, SLOT(_q_newHsv(int,int,int))); + + rightLay->addStretch(); + + cs = new QColorShower(q); + QObject::connect(cs, SIGNAL(newCol(QRgb)), q, SLOT(_q_newColorTypedIn(QRgb))); + QObject::connect(cs, SIGNAL(currentColorChanged(QColor)), + q, SIGNAL(currentColorChanged(QColor))); +#if defined(QT_SMALL_COLORDIALOG) + if (!nonTouchUI) + pWidth -= cp->size().width(); + topLay->addWidget(cs); +#else + rightLay->addWidget(cs); +#endif + + buttons = new QDialogButtonBox(q); + mainLay->addWidget(buttons); + + ok = buttons->addButton(QDialogButtonBox::Ok); + QObject::connect(ok, SIGNAL(clicked()), q, SLOT(accept())); + ok->setDefault(true); + cancel = buttons->addButton(QDialogButtonBox::Cancel); + QObject::connect(cancel, SIGNAL(clicked()), q, SLOT(reject())); + + retranslateStrings(); + +#ifdef Q_WS_MAC + delegate = 0; +#endif + + q->setCurrentColor(initial); +} + +void QColorDialogPrivate::_q_addCustom() +{ + cusrgb[nextCust] = cs->currentColor(); + if (custom) + custom->update(); + nextCust = (nextCust+1) % 16; +} + +void QColorDialogPrivate::retranslateStrings() +{ + if (!smallDisplay) { + lblBasicColors->setText(QColorDialog::tr("&Basic colors")); + lblCustomColors->setText(QColorDialog::tr("&Custom colors")); + addCusBt->setText(QColorDialog::tr("&Add to Custom Colors")); + } + + cs->retranslateStrings(); +} + +static const Qt::WindowFlags DefaultWindowFlags = + Qt::Dialog | Qt::WindowTitleHint | Qt::MSWindowsFixedSizeDialogHint + | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + +/*! + \class QColorDialog + \brief The QColorDialog class provides a dialog widget for specifying colors. + + \ingroup standard-dialogs + + The color dialog's function is to allow users to choose colors. + For example, you might use this in a drawing program to allow the + user to set the brush color. + + The static functions provide modal color dialogs. + \omit + If you require a modeless dialog, use the QColorDialog constructor. + \endomit + + The static getColor() function shows the dialog, and allows the user to + specify a color. This function can also be used to let users choose a + color with a level of transparency: pass the ShowAlphaChannel option as + an additional argument. + + The user can store customCount() different custom colors. The + custom colors are shared by all color dialogs, and remembered + during the execution of the program. Use setCustomColor() to set + the custom colors, and use customColor() to get them. + + Additional widgets that allow users to pick colors are available + as \l{Qt Solutions}. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QColorDialog as well as other built-in Qt dialogs. + + \image plastique-colordialog.png A color dialog in the Plastique widget style. + + \sa QColor, QFileDialog, QPrintDialog, QFontDialog, {Standard Dialogs Example} +*/ + +/*! + \since 4.5 + + Constructs a color dialog with the given \a parent. +*/ +QColorDialog::QColorDialog(QWidget *parent) + : QDialog(*new QColorDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QColorDialog); + d->init(Qt::white); +} + +/*! + \since 4.5 + + Constructs a color dialog with the given \a parent and specified + \a initial color. +*/ +QColorDialog::QColorDialog(const QColor &initial, QWidget *parent) + : QDialog(*new QColorDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QColorDialog); + d->init(initial); +} + +/*! + \property QColorDialog::currentColor + \brief the currently selected color in the dialog +*/ + +void QColorDialog::setCurrentColor(const QColor &color) +{ + Q_D(QColorDialog); + d->setCurrentColor(color.rgb()); + d->selectColor(color); + d->setCurrentAlpha(color.alpha()); + +#ifdef Q_WS_MAC + d->setCurrentQColor(color); + d->setCocoaPanelColor(color); +#endif + if (d->nativeDialogInUse) + qt_guiPlatformPlugin()->colorDialogSetCurrentColor(this, color); +} + +QColor QColorDialog::currentColor() const +{ + Q_D(const QColorDialog); + return d->currentQColor(); +} + + +/*! + Returns the color that the user selected by clicking the \gui{OK} + or equivalent button. + + \note This color is not always the same as the color held by the + \l currentColor property since the user can choose different colors + before finally selecting the one to use. +*/ +QColor QColorDialog::selectedColor() const +{ + Q_D(const QColorDialog); + return d->selectedQColor; +} + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QColorDialog::setOption(ColorDialogOption option, bool on) +{ + Q_D(QColorDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + \since 4.5 + + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QColorDialog::testOption(ColorDialogOption option) const +{ + Q_D(const QColorDialog); + return (d->opts & option) != 0; +} + +/*! + \property QColorDialog::options + \brief the various options that affect the look and feel of the dialog + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QColorDialog::setOptions(ColorDialogOptions options) +{ + Q_D(QColorDialog); + + ColorDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->buttons->setVisible(!(options & NoButtons)); + d->showAlpha(options & ShowAlphaChannel); +} + +QColorDialog::ColorDialogOptions QColorDialog::options() const +{ + Q_D(const QColorDialog); + return d->opts; +} + +/*! + \enum QColorDialog::ColorDialogOption + + \since 4.5 + + This enum specifies various options that affect the look and feel + of a color dialog. + + \value ShowAlphaChannel Allow the user to select the alpha component of a color. + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value DontUseNativeDialog Use Qt's standard color dialog on the Mac instead of Apple's + native color panel. + + \sa options, setOption(), testOption(), windowModality() +*/ + +/*! + \fn void QColorDialog::currentColorChanged(const QColor &color) + + This signal is emitted whenever the current color changes in the dialog. + The current color is specified by \a color. + + \sa color, colorSelected() +*/ + +#ifdef Q_WS_MAC +// can only have one Cocoa color panel active +bool QColorDialogPrivate::sharedColorPanelAvailable = true; +#endif + +/*! + \fn void QColorDialog::colorSelected(const QColor &color); + + This signal is emitted just after the user has clicked \gui{OK} to + select a color to use. The chosen color is specified by \a color. + + \sa color, currentColorChanged() +*/ + +/*! + Changes the visibility of the dialog. If \a visible is true, the dialog + is shown; otherwise, it is hidden. +*/ +void QColorDialog::setVisible(bool visible) +{ + Q_D(QColorDialog); + + if (visible){ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + + if (visible) + d->selectedQColor = QColor(); + +#if defined(Q_WS_MAC) + if (visible) { + if (d->delegate || (QColorDialogPrivate::sharedColorPanelAvailable && + !(testAttribute(Qt::WA_DontShowOnScreen) || (d->opts & DontUseNativeDialog)))){ + d->openCocoaColorPanel(currentColor(), parentWidget(), windowTitle(), options()); + QColorDialogPrivate::sharedColorPanelAvailable = false; + setAttribute(Qt::WA_DontShowOnScreen); + } + setWindowFlags(windowModality() == Qt::WindowModal ? Qt::Sheet : DefaultWindowFlags); + } else { + if (d->delegate) { + d->closeCocoaColorPanel(); + setAttribute(Qt::WA_DontShowOnScreen, false); + } + } +#else + + if (!(d->opts & DontUseNativeDialog) && qt_guiPlatformPlugin()->colorDialogSetVisible(this, visible)) { + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); + } +#endif + + QDialog::setVisible(visible); +} + +/*! + \overload + \since 4.5 + + Opens the dialog and connects its colorSelected() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QColorDialog::open(QObject *receiver, const char *member) +{ + Q_D(QColorDialog); + connect(this, SIGNAL(colorSelected(QColor)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \fn QColorDialog::open() + + \since 4.5 + Shows the dialog as a \l{QDialog#Modal Dialogs}{window modal dialog}, + returning immediately. + + \sa QDialog::open() +*/ + +/* + For Symbian color dialogs +*/ +#ifdef Q_WS_S60 +extern QColor qtSymbianGetColor(const QColor &initial); +#endif +/*! + \since 4.5 + + Pops up a modal color dialog with the given window \a title (or "Select Color" if none is + specified), lets the user choose a color, and returns that color. The color is initially set + to \a initial. The dialog is a child of \a parent. It returns an invalid (see + QColor::isValid()) color if the user cancels the dialog. + + The \a options argument allows you to customize the dialog. + + On Symbian, this static function will use the native color dialog and not a QColorDialog. + On Symbian the parameters \a title and \a parent has no relevance and the + \a options parameter is only used to define if the native color dialog is + used or not. +*/ +QColor QColorDialog::getColor(const QColor &initial, QWidget *parent, const QString &title, + ColorDialogOptions options) +{ +#ifdef Q_WS_S60 + if (!(options & DontUseNativeDialog)) + return qtSymbianGetColor(initial); +#endif + QColorDialog dlg(parent); + if (!title.isEmpty()) + dlg.setWindowTitle(title); + dlg.setOptions(options); + dlg.setCurrentColor(initial); + dlg.exec(); + return dlg.selectedColor(); +} + +/*! + Pops up a modal color dialog, lets the user choose a color, and + returns that color. The color is initially set to \a initial. The + dialog is a child of \a parent. It returns an invalid (see + QColor::isValid()) color if the user cancels the dialog. + + On Symbian, this static function will use the native + color dialog and not a QColorDialog. +*/ + +QColor QColorDialog::getColor(const QColor &initial, QWidget *parent) +{ +#ifdef Q_WS_S60 + return qtSymbianGetColor(initial); +#endif + return getColor(initial, parent, QString(), ColorDialogOptions(0)); +} + + +/*! + \obsolete + + Pops up a modal color dialog to allow the user to choose a color + and an alpha channel (transparency) value. The color+alpha is + initially set to \a initial. The dialog is a child of \a parent. + + If \a ok is non-null, \e *\a ok is set to true if the user clicked + \gui{OK}, and to false if the user clicked Cancel. + + If the user clicks Cancel, the \a initial value is returned. + + Use QColorDialog::getColor() instead, passing the + QColorDialog::ShowAlphaChannel option. +*/ + +QRgb QColorDialog::getRgba(QRgb initial, bool *ok, QWidget *parent) +{ + QColor color(getColor(QColor(initial), parent, QString(), ShowAlphaChannel)); + QRgb result = color.isValid() ? color.rgba() : initial; + if (ok) + *ok = color.isValid(); + return result; +} + +/*! + Destroys the color dialog. +*/ + +QColorDialog::~QColorDialog() +{ + Q_D(QColorDialog); +#if defined(Q_WS_MAC) + if (d->delegate) { + d->releaseCocoaColorPanelDelegate(); + QColorDialogPrivate::sharedColorPanelAvailable = true; + } +#endif + +#ifndef QT_NO_SETTINGS + if (!customSet) { + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + for (int i = 0; i < 2*8; ++i) + settings.setValue(QLatin1String("Qt/customColors/") + QString::number(i), cusrgb[i]); + } +#endif + if (d->nativeDialogInUse) + qt_guiPlatformPlugin()->colorDialogDelete(this); + +} + + +/*! + \reimp +*/ +void QColorDialog::changeEvent(QEvent *e) +{ + Q_D(QColorDialog); + if (e->type() == QEvent::LanguageChange) + d->retranslateStrings(); + QDialog::changeEvent(e); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QColorDialog::done(int result) +{ + Q_D(QColorDialog); + QDialog::done(result); + if (result == Accepted) { + d->selectedQColor = d->currentQColor(); + emit colorSelected(d->selectedQColor); + } else { + d->selectedQColor = QColor(); + } + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(colorSelected(QColor)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +QT_END_NAMESPACE + +#include "qcolordialog.moc" +#include "moc_qcolordialog.cpp" + +#endif // QT_NO_COLORDIALOG + +/*! + \fn QColor QColorDialog::getColor(const QColor &init, QWidget *parent, const char *name) + \compat +*/ + +/*! + \fn QRgb QColorDialog::getRgba(QRgb rgba, bool *ok, QWidget *parent, const char *name) + \compat +*/ diff --git a/src/widgets/dialogs/qcolordialog.h b/src/widgets/dialogs/qcolordialog.h new file mode 100644 index 0000000000..2eb4658634 --- /dev/null +++ b/src/widgets/dialogs/qcolordialog.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORDIALOG_H +#define QCOLORDIALOG_H + +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COLORDIALOG + +class QColorDialogPrivate; + +class Q_WIDGETS_EXPORT QColorDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QColorDialog) + Q_ENUMS(ColorDialogOption) + Q_PROPERTY(QColor currentColor READ currentColor WRITE setCurrentColor + NOTIFY currentColorChanged) + Q_PROPERTY(ColorDialogOptions options READ options WRITE setOptions) + +public: + enum ColorDialogOption { + ShowAlphaChannel = 0x00000001, + NoButtons = 0x00000002, + DontUseNativeDialog = 0x00000004 + }; + + Q_DECLARE_FLAGS(ColorDialogOptions, ColorDialogOption) + + explicit QColorDialog(QWidget *parent = 0); + explicit QColorDialog(const QColor &initial, QWidget *parent = 0); + ~QColorDialog(); + + void setCurrentColor(const QColor &color); + QColor currentColor() const; + + QColor selectedColor() const; + + void setOption(ColorDialogOption option, bool on = true); + bool testOption(ColorDialogOption option) const; + void setOptions(ColorDialogOptions options); + ColorDialogOptions options() const; + +#ifdef Q_NO_USING_KEYWORD + void open() { QDialog::open(); } +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + void setVisible(bool visible); + + // ### Qt 5: merge overloads with title = QString() + static QColor getColor(const QColor &initial, QWidget *parent, const QString &title, + ColorDialogOptions options = 0); + static QColor getColor(const QColor &initial = Qt::white, QWidget *parent = 0); + + // obsolete + static QRgb getRgba(QRgb rgba = 0xffffffff, bool *ok = 0, QWidget *parent = 0); + + // ### Qt 5: use QColor in signatures + static int customCount(); + static QRgb customColor(int index); + static void setCustomColor(int index, QRgb color); + static void setStandardColor(int index, QRgb color); + +#ifdef QT3_SUPPORT + static QColor getColor(const QColor &init, QWidget *parent, const char *name) + { Q_UNUSED(name); return getColor(init, parent); } + static QRgb getRgba(QRgb rgba, bool *ok, QWidget *parent, const char *name) + { Q_UNUSED(name); return getRgba(rgba, ok, parent); } +#endif + +Q_SIGNALS: + void currentColorChanged(const QColor &color); + void colorSelected(const QColor &color); + +protected: + void changeEvent(QEvent *event); + void done(int result); + +private: + Q_DISABLE_COPY(QColorDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_addCustom()) + Q_PRIVATE_SLOT(d_func(), void _q_newHsv(int h, int s, int v)) + Q_PRIVATE_SLOT(d_func(), void _q_newColorTypedIn(QRgb rgb)) + Q_PRIVATE_SLOT(d_func(), void _q_newCustom(int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_newStandard(int, int)) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif + + friend class QColorShower; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QColorDialog::ColorDialogOptions) + +#endif // QT_NO_COLORDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLORDIALOG_H diff --git a/src/widgets/dialogs/qcolordialog_mac.mm b/src/widgets/dialogs/qcolordialog_mac.mm new file mode 100644 index 0000000000..1d77751e2b --- /dev/null +++ b/src/widgets/dialogs/qcolordialog_mac.mm @@ -0,0 +1,505 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" +#if !defined(QT_NO_COLORDIALOG) && defined(Q_WS_MAC) +#include <qapplication.h> +#include <qtimer.h> +#include <qdialogbuttonbox.h> +#include <qabstracteventdispatcher.h> +#include <private/qapplication_p.h> +#include <private/qt_mac_p.h> +#include <qdebug.h> +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#if !CGFLOAT_DEFINED +typedef float CGFloat; // Should only not be defined on 32-bit platforms +#endif + + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSWindowDelegate <NSObject> +- (void)windowDidResize:(NSNotification *)notification; +- (BOOL)windowShouldClose:(id)window; +@end +#endif + +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate); + +@interface QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) : NSObject<NSWindowDelegate> { + NSColorPanel *mColorPanel; + NSView *mStolenContentView; + NSButton *mOkButton; + NSButton *mCancelButton; + QColorDialogPrivate *mPriv; + QColor *mQtColor; + CGFloat mMinWidth; // currently unused + CGFloat mExtraHeight; // currently unused + BOOL mHackedPanel; + NSInteger mResultCode; + BOOL mDialogIsExecuting; + BOOL mResultSet; +} +- (id)initWithColorPanel:(NSColorPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QColorDialogPrivate *)priv; +- (void)colorChanged:(NSNotification *)notification; +- (void)relayout; +- (void)onOkClicked; +- (void)onCancelClicked; +- (void)updateQtColor; +- (NSColorPanel *)colorPanel; +- (QColor)qtColor; +- (void)finishOffWithCode:(NSInteger)result; +- (void)showColorPanel; +- (void)exec; +- (void)setResultSet:(BOOL)result; +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) +- (id)initWithColorPanel:(NSColorPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QColorDialogPrivate *)priv +{ + self = [super init]; + + mColorPanel = panel; + mStolenContentView = stolenContentView; + mOkButton = okButton; + mCancelButton = cancelButton; + mPriv = priv; + mMinWidth = 0.0; + mExtraHeight = 0.0; + mHackedPanel = (okButton != 0); + mResultCode = NSCancelButton; + mDialogIsExecuting = false; + mResultSet = false; + + if (mHackedPanel) { + [self relayout]; + + [okButton setAction:@selector(onOkClicked)]; + [okButton setTarget:self]; + + [cancelButton setAction:@selector(onCancelClicked)]; + [cancelButton setTarget:self]; + } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(colorChanged:) + name:NSColorPanelColorDidChangeNotification + object:mColorPanel]; + + mQtColor = new QColor(); + return self; +} + +- (void)dealloc +{ + QMacCocoaAutoReleasePool pool; + if (mHackedPanel) { + NSView *ourContentView = [mColorPanel contentView]; + + // return stolen stuff to its rightful owner + [mStolenContentView removeFromSuperview]; + [mColorPanel setContentView:mStolenContentView]; + + [mOkButton release]; + [mCancelButton release]; + [ourContentView release]; + } + [mColorPanel setDelegate:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + delete mQtColor; + [super dealloc]; +} + +- (void)setResultSet:(BOOL)result +{ + mResultSet = result; +} + +- (BOOL)windowShouldClose:(id)window +{ + Q_UNUSED(window); + if (!mHackedPanel) + [self updateQtColor]; + if (mDialogIsExecuting) { + [self finishOffWithCode:NSCancelButton]; + } else { + mResultSet = true; + mPriv->colorDialog()->reject(); + } + return true; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (mHackedPanel) + [self relayout]; +} + +- (void)colorChanged:(NSNotification *)notification +{ + Q_UNUSED(notification); + [self updateQtColor]; +} + +- (void)relayout +{ + Q_ASSERT(mHackedPanel); + + NSRect rect = [[mStolenContentView superview] frame]; + + // should a priori be kept in sync with qfontdialog_mac.mm + const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon + const CGFloat ButtonMinHeight = 32.0; + const CGFloat ButtonSpacing = 0.0; + const CGFloat ButtonTopMargin = 0.0; + const CGFloat ButtonBottomMargin = 7.0; + const CGFloat ButtonSideMargin = 9.0; + + [mOkButton sizeToFit]; + NSSize okSizeHint = [mOkButton frame].size; + + [mCancelButton sizeToFit]; + NSSize cancelSizeHint = [mCancelButton frame].size; + + const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth, + qMax(okSizeHint.width, cancelSizeHint.width)), + CGFloat((rect.size.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5)); + const CGFloat ButtonHeight = qMax(ButtonMinHeight, + qMax(okSizeHint.height, cancelSizeHint.height)); + + NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mOkButton setFrame:okRect]; + [mOkButton setNeedsDisplay:YES]; + + NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mCancelButton setFrame:cancelRect]; + [mCancelButton setNeedsDisplay:YES]; + + const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin; + NSRect stolenCVRect = { { 0.0, Y }, + { rect.size.width, rect.size.height - Y } }; + [mStolenContentView setFrame:stolenCVRect]; + [mStolenContentView setNeedsDisplay:YES]; + + [[mStolenContentView superview] setNeedsDisplay:YES]; + mMinWidth = 2 * ButtonSideMargin + ButtonSpacing + 2 * ButtonWidth; + mExtraHeight = Y; +} + +- (void)onOkClicked +{ + Q_ASSERT(mHackedPanel); + [[mStolenContentView window] close]; + [self updateQtColor]; + [self finishOffWithCode:NSOKButton]; +} + +- (void)onCancelClicked +{ + if (mHackedPanel) { + [[mStolenContentView window] close]; + delete mQtColor; + mQtColor = new QColor(); + [self finishOffWithCode:NSCancelButton]; + } +} + +- (void)updateQtColor +{ + delete mQtColor; + mQtColor = new QColor(); + NSColor *color = [mColorPanel color]; + NSString *colorSpaceName = [color colorSpaceName]; + if (colorSpaceName == NSDeviceCMYKColorSpace) { + CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0; + [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha]; + mQtColor->setCmykF(cyan, magenta, yellow, black, alpha); + } else if (colorSpaceName == NSCalibratedRGBColorSpace || colorSpaceName == NSDeviceRGBColorSpace) { + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } else if (colorSpaceName == NSNamedColorSpace) { + NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } else { + NSColorSpace *colorSpace = [color colorSpace]; + if ([colorSpace colorSpaceModel] == NSCMYKColorSpaceModel && [color numberOfComponents] == 5){ + CGFloat components[5]; + [color getComponents:components]; + mQtColor->setCmykF(components[0], components[1], components[2], components[3], components[4]); + } else { + NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } + } + + mPriv->setCurrentQColor(*mQtColor); +} + +- (NSColorPanel *)colorPanel +{ + return mColorPanel; +} + +- (QColor)qtColor +{ + return *mQtColor; +} + +- (void)finishOffWithCode:(NSInteger)code +{ + mResultCode = code; + if (mDialogIsExecuting) { + // We stop the current modal event loop. The control + // will then return inside -(void)exec below. + // It's important that the modal event loop is stopped before + // we accept/reject QColorDialog, since QColorDialog has its + // own event loop that needs to be stopped last. + [NSApp stopModalWithCode:code]; + } else { + // Since we are not in a modal event loop, we can safely close + // down QColorDialog + // Calling accept() or reject() can in turn call closeCocoaColorPanel. + // This check will prevent any such recursion. + if (!mResultSet) { + mResultSet = true; + if (mResultCode == NSCancelButton) { + mPriv->colorDialog()->reject(); + } else { + mPriv->colorDialog()->accept(); + } + } + } +} + +- (void)showColorPanel +{ + mDialogIsExecuting = false; + [mColorPanel makeKeyAndOrderFront:mColorPanel]; +} + +- (void)exec +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); + QMacCocoaAutoReleasePool pool; + mDialogIsExecuting = true; + bool modalEnded = false; + while (!modalEnded) { +#ifndef QT_NO_EXCEPTIONS + @try { + [NSApp runModalForWindow:mColorPanel]; + modalEnded = true; + } @catch (NSException *) { + // For some reason, NSColorPanel throws an exception when + // clicking on 'SelectedMenuItemColor' from the 'Developer' + // palette (tab three). + } +#else + [NSApp runModalForWindow:mColorPanel]; + modalEnded = true; +#endif + } + + QAbstractEventDispatcher::instance()->interrupt(); + if (mResultCode == NSCancelButton) + mPriv->colorDialog()->reject(); + else + mPriv->colorDialog()->accept(); +} + +@end + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptNSPanelCtor(); +extern void macStopInterceptNSPanelCtor(); +extern NSButton *macCreateButton(const char *text, NSView *superview); + +void QColorDialogPrivate::openCocoaColorPanel(const QColor &initial, + QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options) +{ + Q_UNUSED(parent); // we would use the parent if only NSColorPanel could be a sheet + QMacCocoaAutoReleasePool pool; + + if (!delegate) { + /* + The standard Cocoa color panel has no OK or Cancel button and + is created as a utility window, whereas we want something like + the Carbon color panel. We need to take the following steps: + + 1. Intercept the color panel constructor to turn off the + NSUtilityWindowMask flag. This is done by temporarily + replacing initWithContentRect:styleMask:backing:defer: + in NSPanel by our own method. + + 2. Modify the color panel so that its content view is part + of a new content view that contains it as well as two + buttons (OK and Cancel). + + 3. Lay out the original content view and the buttons when + the color panel is shown and whenever it is resized. + + 4. Clean up after ourselves. + */ + + bool hackColorPanel = !(options & QColorDialog::NoButtons); + + if (hackColorPanel) + macStartInterceptNSPanelCtor(); + NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel]; + if (hackColorPanel) + macStopInterceptNSPanelCtor(); + + [colorPanel setHidesOnDeactivate:false]; + + // set up the Cocoa color panel + [colorPanel setShowsAlpha:options & QColorDialog::ShowAlphaChannel]; + [colorPanel setTitle:(NSString*)(CFStringRef)QCFString(title)]; + + NSView *stolenContentView = 0; + NSButton *okButton = 0; + NSButton *cancelButton = 0; + + if (hackColorPanel) { + // steal the color panel's contents view + stolenContentView = [colorPanel contentView]; + [stolenContentView retain]; + [colorPanel setContentView:0]; + + // create a new content view and add the stolen one as a subview + NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect]; + [ourContentView addSubview:stolenContentView]; + + // create OK and Cancel buttons and add these as subviews + okButton = macCreateButton("&OK", ourContentView); + cancelButton = macCreateButton("Cancel", ourContentView); + + [colorPanel setContentView:ourContentView]; + [colorPanel setDefaultButtonCell:[okButton cell]]; + } + + delegate = [[QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) alloc] initWithColorPanel:colorPanel + stolenContentView:stolenContentView + okButton:okButton + cancelButton:cancelButton + priv:this]; + [colorPanel setDelegate:static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate)]; + } + [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) setResultSet:NO]; + setCocoaPanelColor(initial); + [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) showColorPanel]; +} + +void QColorDialogPrivate::closeCocoaColorPanel() +{ + [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) onCancelClicked]; +} + +void QColorDialogPrivate::releaseCocoaColorPanelDelegate() +{ + [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) release]; +} + +void QColorDialogPrivate::mac_nativeDialogModalHelp() +{ + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most QEventLoop has been + // interrupted, and the second-most event loop has not yet been reactivated (regardless + // if [NSApp run] is still on the stack)), showing a native modal dialog will fail. + if (delegate){ + Q_Q(QColorDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +void QColorDialogPrivate::_q_macRunNativeAppModalPanel() +{ + [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) exec]; +} + +void QColorDialogPrivate::setCocoaPanelColor(const QColor &color) +{ + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate); + NSColor *nsColor; + const QColor::Spec spec = color.spec(); + if (spec == QColor::Cmyk) { + nsColor = [NSColor colorWithDeviceCyan:color.cyanF() + magenta:color.magentaF() + yellow:color.yellowF() + black:color.blackF() + alpha:color.alphaF()]; + } else { + nsColor = [NSColor colorWithCalibratedRed:color.redF() + green:color.greenF() + blue:color.blueF() + alpha:color.alphaF()]; + } + [[theDelegate colorPanel] setColor:nsColor]; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qcolordialog_p.h b/src/widgets/dialogs/qcolordialog_p.h new file mode 100644 index 0000000000..f1865e66ec --- /dev/null +++ b/src/widgets/dialogs/qcolordialog_p.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORDIALOG_P_H +#define QCOLORDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qdialog_p.h" +#include "qcolordialog.h" + +#ifndef QT_NO_COLORDIALOG + +QT_BEGIN_NAMESPACE + +class QColorLuminancePicker; +class QColorPicker; +class QColorShower; +class QDialogButtonBox; +class QLabel; +class QVBoxLayout; +class QPushButton; +class QWellArray; + +class QColorDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QColorDialog) + +public: + void init(const QColor &initial); + QRgb currentColor() const; + QColor currentQColor() const; + void setCurrentColor(QRgb rgb); + void setCurrentQColor(const QColor &color); + bool selectColor(const QColor &color); + + int currentAlpha() const; + void setCurrentAlpha(int a); + void showAlpha(bool b); + bool isAlphaVisible() const; + void retranslateStrings(); + + void _q_addCustom(); + + void _q_newHsv(int h, int s, int v); + void _q_newColorTypedIn(QRgb rgb); + void _q_newCustom(int, int); + void _q_newStandard(int, int); + + QWellArray *custom; + QWellArray *standard; + + QDialogButtonBox *buttons; + QVBoxLayout *leftLay; + QColorPicker *cp; + QColorLuminancePicker *lp; + QColorShower *cs; + QLabel *lblBasicColors; + QLabel *lblCustomColors; + QPushButton *ok; + QPushButton *cancel; + QPushButton *addCusBt; + QColor selectedQColor; + int nextCust; + bool smallDisplay; + QColorDialog::ColorDialogOptions opts; + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + bool nativeDialogInUse; + +#ifdef Q_WS_MAC + void openCocoaColorPanel(const QColor &initial, + QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options); + void closeCocoaColorPanel(); + void releaseCocoaColorPanelDelegate(); + void setCocoaPanelColor(const QColor &color); + + inline void done(int result) { q_func()->done(result); } + inline QColorDialog *colorDialog() { return q_func(); } + + void *delegate; + + static bool sharedColorPanelAvailable; + + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); +#endif +}; + +#endif // QT_NO_COLORDIALOG + +QT_END_NAMESPACE + +#endif // QCOLORDIALOG_P_H diff --git a/src/widgets/dialogs/qcolordialog_symbian.cpp b/src/widgets/dialogs/qcolordialog_symbian.cpp new file mode 100644 index 0000000000..eab35f5ac8 --- /dev/null +++ b/src/widgets/dialogs/qcolordialog_symbian.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" + +#ifndef QT_NO_COLORDIALOG + + +#include "qcolor.h" +#include "private/qguiplatformplugin_p.h" + +#ifdef Q_WS_S60 +#include <AknColourSelectionGrid.h> +#endif + +#include "private/qt_s60_p.h" + +QT_BEGIN_NAMESPACE + +QColor launchSymbianColorDialog(QColor initial) +{ + QColor currentColor = QColor::Invalid; +#ifdef Q_WS_S60 + QT_TRAP_THROWING( + CArrayFixFlat<TRgb>* array = new( ELeave ) CArrayFixFlat<TRgb>(17); + CleanupStack::PushL(array); + array->AppendL(KRgbBlack); + array->AppendL(KRgbDarkGray); + array->AppendL(KRgbDarkRed); + array->AppendL(KRgbDarkGreen); + array->AppendL(KRgbDarkYellow); + array->AppendL(KRgbDarkBlue); + array->AppendL(KRgbDarkMagenta); + array->AppendL(KRgbDarkCyan); + array->AppendL(KRgbRed); + array->AppendL(KRgbGreen); + array->AppendL(KRgbYellow); + array->AppendL(KRgbBlue); + array->AppendL(KRgbMagenta); + array->AppendL(KRgbCyan); + array->AppendL(KRgbGray); + array->AppendL(KRgbWhite); + + TRgb initialColour(initial.red(), initial.green(), initial.blue(), initial.alpha()); + + TBool noneChosen = EFalse; // If true shows the default colour button + CAknColourSelectionGrid* colourSelectionGrid = + CAknColourSelectionGrid::NewL(array, EFalse, noneChosen, initialColour); + CleanupStack::PushL(colourSelectionGrid); + + if (colourSelectionGrid->ExecuteLD()) { + currentColor.setRgb(initialColour.Red(), initialColour.Green(), + initialColour.Blue(), initialColour.Alpha()); + } + CleanupStack::Pop(colourSelectionGrid); + CleanupStack::PopAndDestroy(array); + ); +#endif + return currentColor; +} + +QColor qtSymbianGetColor(const QColor &initial) +{ + return launchSymbianColorDialog(initial); +} + +QT_END_NAMESPACE + +#endif // QT_NO_COLORDIALOG diff --git a/src/widgets/dialogs/qdialog.cpp b/src/widgets/dialogs/qdialog.cpp new file mode 100644 index 0000000000..2fb6c67e50 --- /dev/null +++ b/src/widgets/dialogs/qdialog.cpp @@ -0,0 +1,1272 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdialog.h" + + +#include "qevent.h" +#include "qdesktopwidget.h" +#include "qpushbutton.h" +#include "qapplication.h" +#include "qlayout.h" +#include "qsizegrip.h" +#include "qwhatsthis.h" +#include "qmenu.h" +#include "qcursor.h" +#include "private/qdialog_p.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#if defined(Q_WS_WINCE) +#include "qt_windows.h" +#include "qmenubar.h" +#include "qpointer.h" +#include "qguifunctions_wince.h" +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp +extern bool qt_wince_is_smartphone(); //is defined in qguifunctions_wce.cpp +#elif defined(Q_WS_X11) +# include "../kernel/qt_x11_p.h" +#elif defined(Q_OS_SYMBIAN) +# include "qfiledialog.h" +# include "qfontdialog.h" +# include "qwizard.h" +# include "private/qt_s60_p.h" +#endif + +#if defined(Q_WS_S60) +#include <AknUtils.h> // AknLayoutUtils +#endif + +#ifndef SPI_GETSNAPTODEFBUTTON +# define SPI_GETSNAPTODEFBUTTON 95 +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QDialog + \brief The QDialog class is the base class of dialog windows. + + \ingroup dialog-classes + \ingroup abstractwidgets + + + A dialog window is a top-level window mostly used for short-term + tasks and brief communications with the user. QDialogs may be + modal or modeless. QDialogs can + provide a \link #return return + value\endlink, and they can have \link #default default + buttons\endlink. QDialogs can also have a QSizeGrip in their + lower-right corner, using setSizeGripEnabled(). + + Note that QDialog (an any other widget that has type Qt::Dialog) uses + the parent widget slightly differently from other classes in Qt. A + dialog is always a top-level widget, but if it has a parent, its + default location is centered on top of the parent's top-level widget + (if it is not top-level itself). It will also share the parent's + taskbar entry. + + Use the overload of the QWidget::setParent() function to change + the ownership of a QDialog widget. This function allows you to + explicitly set the window flags of the reparented widget; using + the overloaded function will clear the window flags specifying the + window-system properties for the widget (in particular it will + reset the Qt::Dialog flag). + + \section1 Modal Dialogs + + A \bold{modal} dialog is a dialog that blocks input to other + visible windows in the same application. Dialogs that are used to + request a file name from the user or that are used to set + application preferences are usually modal. Dialogs can be + \l{Qt::ApplicationModal}{application modal} (the default) or + \l{Qt::WindowModal}{window modal}. + + When an application modal dialog is opened, the user must finish + interacting with the dialog and close it before they can access + any other window in the application. Window modal dialogs only + block access to the window associated with the dialog, allowing + the user to continue to use other windows in an application. + + The most common way to display a modal dialog is to call its + exec() function. When the user closes the dialog, exec() will + provide a useful \link #return return value\endlink. Typically, + to get the dialog to close and return the appropriate value, we + connect a default button, e.g. \gui OK, to the accept() slot and a + \gui Cancel button to the reject() slot. + Alternatively you can call the done() slot with \c Accepted or + \c Rejected. + + An alternative is to call setModal(true) or setWindowModality(), + then show(). Unlike exec(), show() returns control to the caller + immediately. Calling setModal(true) is especially useful for + progress dialogs, where the user must have the ability to interact + with the dialog, e.g. to cancel a long running operation. If you + use show() and setModal(true) together to perform a long operation, + you must call QApplication::processEvents() periodically during + processing to enable the user to interact with the dialog. (See + QProgressDialog.) + + \section1 Modeless Dialogs + + A \bold{modeless} dialog is a dialog that operates + independently of other windows in the same application. Find and + replace dialogs in word-processors are often modeless to allow the + user to interact with both the application's main window and with + the dialog. + + Modeless dialogs are displayed using show(), which returns control + to the caller immediately. + + If you invoke the \l{QWidget::show()}{show()} function after hiding + a dialog, the dialog will be displayed in its original position. This is + because the window manager decides the position for windows that + have not been explicitly placed by the programmer. To preserve the + position of a dialog that has been moved by the user, save its position + in your \l{QWidget::closeEvent()}{closeEvent()} handler and then + move the dialog to that position, before showing it again. + + \target default + \section1 Default Button + + A dialog's \e default button is the button that's pressed when the + user presses Enter (Return). This button is used to signify that + the user accepts the dialog's settings and wants to close the + dialog. Use QPushButton::setDefault(), QPushButton::isDefault() + and QPushButton::autoDefault() to set and control the dialog's + default button. + + \target escapekey + \section1 Escape Key + + If the user presses the Esc key in a dialog, QDialog::reject() + will be called. This will cause the window to close: The \link + QCloseEvent close event \endlink cannot be \link + QCloseEvent::ignore() ignored \endlink. + + \section1 Extensibility + + Extensibility is the ability to show the dialog in two ways: a + partial dialog that shows the most commonly used options, and a + full dialog that shows all the options. Typically an extensible + dialog will initially appear as a partial dialog, but with a + \gui More toggle button. If the user presses the \gui More button down, + the dialog is expanded. The \l{Extension Example} shows how to achieve + extensible dialogs using Qt. + + \target return + \section1 Return Value (Modal Dialogs) + + Modal dialogs are often used in situations where a return value is + required, e.g. to indicate whether the user pressed \gui OK or + \gui Cancel. A dialog can be closed by calling the accept() or the + reject() slots, and exec() will return \c Accepted or \c Rejected + as appropriate. The exec() call returns the result of the dialog. + The result is also available from result() if the dialog has not + been destroyed. + + In order to modify your dialog's close behavior, you can reimplement + the functions accept(), reject() or done(). The + \l{QWidget::closeEvent()}{closeEvent()} function should only be + reimplemented to preserve the dialog's position or to override the + standard close or reject behavior. + + \target examples + \section1 Code Examples + + A modal dialog: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 1 + + A modeless dialog: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 0 + + \sa QDialogButtonBox, QTabWidget, QWidget, QProgressDialog, + {fowler}{GUI Design Handbook: Dialogs, Standard}, {Extension Example}, + {Standard Dialogs Example} +*/ + +/*! \enum QDialog::DialogCode + + The value returned by a modal dialog. + + \value Accepted + \value Rejected +*/ + +/*! + \property QDialog::sizeGripEnabled + \brief whether the size grip is enabled + + A QSizeGrip is placed in the bottom-right corner of the dialog when this + property is enabled. By default, the size grip is disabled. +*/ + + +/*! + Constructs a dialog with parent \a parent. + + A dialog is always a top-level widget, but if it has a parent, its + default location is centered on top of the parent. It will also + share the parent's taskbar entry. + + The widget flags \a f are passed on to the QWidget constructor. + If, for example, you don't want a What's This button in the title bar + of the dialog, pass Qt::WindowTitleHint | Qt::WindowSystemMenuHint in \a f. + + \sa QWidget::setWindowFlags() +*/ + +QDialog::QDialog(QWidget *parent, Qt::WindowFlags f) + : QWidget(*new QDialogPrivate, parent, + f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0))) +{ +#ifdef Q_WS_WINCE + if (!qt_wince_is_smartphone()) + setWindowFlags(windowFlags() | Qt::WindowOkButtonHint | QFlag(qt_wince_is_mobile() ? 0 : Qt::WindowCancelButtonHint)); +#endif + +#ifdef Q_WS_S60 + if (S60->avkonComponentsSupportTransparency) { + bool noSystemBackground = testAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); // also sets WA_NoSystemBackground + setAttribute(Qt::WA_NoSystemBackground, noSystemBackground); // restore system background attribute + } +#endif +} + +#ifdef QT3_SUPPORT +/*! + \overload + \obsolete +*/ +QDialog::QDialog(QWidget *parent, const char *name, bool modal, Qt::WindowFlags f) + : QWidget(*new QDialogPrivate, parent, + f + | QFlag(modal ? Qt::WShowModal : Qt::WindowType(0)) + | QFlag((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0)) + ) +{ + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + \overload + \internal +*/ +QDialog::QDialog(QDialogPrivate &dd, QWidget *parent, Qt::WindowFlags f) + : QWidget(dd, parent, f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0))) +{ +#ifdef Q_WS_WINCE + if (!qt_wince_is_smartphone()) + setWindowFlags(windowFlags() | Qt::WindowOkButtonHint | QFlag(qt_wince_is_mobile() ? 0 : Qt::WindowCancelButtonHint)); +#endif + +#ifdef Q_WS_S60 + if (S60->avkonComponentsSupportTransparency) { + bool noSystemBackground = testAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); // also sets WA_NoSystemBackground + setAttribute(Qt::WA_NoSystemBackground, noSystemBackground); // restore system background attribute + } +#endif +} + +/*! + Destroys the QDialog, deleting all its children. +*/ + +QDialog::~QDialog() +{ + QT_TRY { + // Need to hide() here, as our (to-be) overridden hide() + // will not be called in ~QWidget. + hide(); + } QT_CATCH(...) { + // we're in the destructor - just swallow the exception + } +} + +/*! + \internal + This function is called by the push button \a pushButton when it + becomes the default button. If \a pushButton is 0, the dialogs + default default button becomes the default button. This is what a + push button calls when it loses focus. +*/ +void QDialogPrivate::setDefault(QPushButton *pushButton) +{ + Q_Q(QDialog); + bool hasMain = false; + QList<QPushButton*> list = q->findChildren<QPushButton*>(); + for (int i=0; i<list.size(); ++i) { + QPushButton *pb = list.at(i); + if (pb->window() == q) { + if (pb == mainDef) + hasMain = true; + if (pb != pushButton) + pb->setDefault(false); + } + } + if (!pushButton && hasMain) + mainDef->setDefault(true); + if (!hasMain) + mainDef = pushButton; +} + +/*! + \internal + This function sets the default default push button to \a pushButton. + This function is called by QPushButton::setDefault(). +*/ +void QDialogPrivate::setMainDefault(QPushButton *pushButton) +{ + mainDef = 0; + setDefault(pushButton); +} + +/*! + \internal + Hides the default button indicator. Called when non auto-default + push button get focus. + */ +void QDialogPrivate::hideDefault() +{ + Q_Q(QDialog); + QList<QPushButton*> list = q->findChildren<QPushButton*>(); + for (int i=0; i<list.size(); ++i) { + list.at(i)->setDefault(false); + } +} + +void QDialogPrivate::resetModalitySetByOpen() +{ + Q_Q(QDialog); + if (resetModalityTo != -1 && !q->testAttribute(Qt::WA_SetWindowModality)) { + // open() changed the window modality and the user didn't touch it afterwards; restore it + q->setWindowModality(Qt::WindowModality(resetModalityTo)); + q->setAttribute(Qt::WA_SetWindowModality, wasModalitySet); +#ifdef Q_WS_MAC + Q_ASSERT(resetModalityTo != Qt::WindowModal); + q->setParent(q->parentWidget(), Qt::Dialog); +#endif + } + resetModalityTo = -1; +} + +#if defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) +#ifdef Q_WS_WINCE_WM +void QDialogPrivate::_q_doneAction() +{ + //Done... + QApplication::postEvent(q_func(), new QEvent(QEvent::OkRequest)); +} +#endif + +/*! + \reimp +*/ +bool QDialog::event(QEvent *e) +{ + bool result = QWidget::event(e); +#ifdef Q_WS_WINCE + if (e->type() == QEvent::OkRequest) { + accept(); + result = true; + } +#elif defined(Q_WS_S60) + if ((e->type() == QEvent::StyleChange) || (e->type() == QEvent::Resize )) { + if (!testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } + } + // TODO is Symbian, non-S60 behaviour required? +#endif + return result; +} +#endif + +/*! + Returns the modal dialog's result code, \c Accepted or \c Rejected. + + Do not call this function if the dialog was constructed with the + Qt::WA_DeleteOnClose attribute. +*/ +int QDialog::result() const +{ + Q_D(const QDialog); + return d->rescode; +} + +/*! + \fn void QDialog::setResult(int i) + + Sets the modal dialog's result code to \a i. + + \note We recommend that you use one of the values defined by + QDialog::DialogCode. +*/ +void QDialog::setResult(int r) +{ + Q_D(QDialog); + d->rescode = r; +} + +/*! + \since 4.5 + + Shows the dialog as a \l{QDialog#Modal Dialogs}{window modal dialog}, + returning immediately. + + \sa exec(), show(), result(), setWindowModality() +*/ +void QDialog::open() +{ + Q_D(QDialog); + + Qt::WindowModality modality = windowModality(); + if (modality != Qt::WindowModal) { + d->resetModalityTo = modality; + d->wasModalitySet = testAttribute(Qt::WA_SetWindowModality); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_SetWindowModality, false); +#ifdef Q_WS_MAC + setParent(parentWidget(), Qt::Sheet); +#endif + } + + setResult(0); + show(); +} + +/*! + Shows the dialog as a \l{QDialog#Modal Dialogs}{modal dialog}, + blocking until the user closes it. The function returns a \l + DialogCode result. + + If the dialog is \l{Qt::ApplicationModal}{application modal}, users cannot + interact with any other window in the same application until they close + the dialog. If the dialog is \l{Qt::ApplicationModal}{window modal}, only + interaction with the parent window is blocked while the dialog is open. + By default, the dialog is application modal. + + \sa open(), show(), result(), setWindowModality() +*/ + +int QDialog::exec() +{ + Q_D(QDialog); + + if (d->eventLoop) { + qWarning("QDialog::exec: Recursive call detected"); + return -1; + } + + bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, false); + + d->resetModalitySetByOpen(); + + bool wasShowModal = testAttribute(Qt::WA_ShowModal); + setAttribute(Qt::WA_ShowModal, true); + setResult(0); + +//On Windows Mobile we create an empty menu to hide the current menu +#ifdef Q_WS_WINCE_WM +#ifndef QT_NO_MENUBAR + QMenuBar *menuBar = 0; + if (!findChild<QMenuBar *>()) + menuBar = new QMenuBar(this); + if (qt_wince_is_smartphone()) { + QAction *doneAction = new QAction(tr("Done"), this); + menuBar->setDefaultAction(doneAction); + connect(doneAction, SIGNAL(triggered()), this, SLOT(_q_doneAction())); + } +#endif //QT_NO_MENUBAR +#endif //Q_WS_WINCE_WM + + bool showSystemDialogFullScreen = false; +#ifdef Q_OS_SYMBIAN + if (qobject_cast<QFileDialog *>(this) || qobject_cast<QFontDialog *>(this) || + qobject_cast<QWizard *>(this)) { + showSystemDialogFullScreen = true; + } +#endif // Q_OS_SYMBIAN + + if (showSystemDialogFullScreen) { + setWindowFlags(windowFlags() | Qt::WindowSoftkeysVisibleHint); + setWindowState(Qt::WindowFullScreen); + } + show(); + +#ifdef Q_WS_MAC + d->mac_nativeDialogModalHelp(); +#endif + + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + QPointer<QDialog> guard = this; + (void) eventLoop.exec(QEventLoop::DialogExec); + if (guard.isNull()) + return QDialog::Rejected; + d->eventLoop = 0; + + setAttribute(Qt::WA_ShowModal, wasShowModal); + + int res = result(); + if (deleteOnClose) + delete this; +#ifdef Q_WS_WINCE_WM +#ifndef QT_NO_MENUBAR + else if (menuBar) + delete menuBar; +#endif //QT_NO_MENUBAR +#endif //Q_WS_WINCE_WM + return res; +} + + +/*! + Closes the dialog and sets its result code to \a r. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a r. + + As with QWidget::close(), done() deletes the dialog if the + Qt::WA_DeleteOnClose flag is set. If the dialog is the application's + main widget, the application terminates. If the dialog is the + last window closed, the QApplication::lastWindowClosed() signal is + emitted. + + \sa accept(), reject(), QApplication::activeWindow(), QApplication::quit() +*/ + +void QDialog::done(int r) +{ + Q_D(QDialog); + hide(); + setResult(r); + + d->close_helper(QWidgetPrivate::CloseNoEvent); + d->resetModalitySetByOpen(); + + emit finished(r); + if (r == Accepted) + emit accepted(); + else if (r == Rejected) + emit rejected(); +} + +/*! + Hides the modal dialog and sets the result code to \c Accepted. + + \sa reject() done() +*/ + +void QDialog::accept() +{ + done(Accepted); +} + +/*! + Hides the modal dialog and sets the result code to \c Rejected. + + \sa accept() done() +*/ + +void QDialog::reject() +{ + done(Rejected); +} + +/*! \reimp */ +bool QDialog::eventFilter(QObject *o, QEvent *e) +{ + return QWidget::eventFilter(o, e); +} + +/***************************************************************************** + Event handlers + *****************************************************************************/ + +#ifndef QT_NO_CONTEXTMENU +/*! \reimp */ +void QDialog::contextMenuEvent(QContextMenuEvent *e) +{ +#if defined(QT_NO_WHATSTHIS) || defined(QT_NO_MENU) + Q_UNUSED(e); +#else + QWidget *w = childAt(e->pos()); + if (!w) { + w = rect().contains(e->pos()) ? this : 0; + if (!w) + return; + } + while (w && w->whatsThis().size() == 0 && !w->testAttribute(Qt::WA_CustomWhatsThis)) + w = w->isWindow() ? 0 : w->parentWidget(); + if (w) { + QWeakPointer<QMenu> p = new QMenu(this); + QAction *wt = p.data()->addAction(tr("What's This?")); + if (p.data()->exec(e->globalPos()) == wt) { + QHelpEvent e(QEvent::WhatsThis, w->rect().center(), + w->mapToGlobal(w->rect().center())); + QApplication::sendEvent(w, &e); + } + delete p.data(); + } +#endif +} +#endif // QT_NO_CONTEXTMENU + +/*! \reimp */ +void QDialog::keyPressEvent(QKeyEvent *e) +{ + // Calls reject() if Escape is pressed. Simulates a button + // click for the default button if Enter is pressed. Move focus + // for the arrow keys. Ignore the rest. +#ifdef Q_WS_MAC + if(e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) { + reject(); + } else +#endif + if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: { + QList<QPushButton*> list = findChildren<QPushButton*>(); + for (int i=0; i<list.size(); ++i) { + QPushButton *pb = list.at(i); + if (pb->isDefault() && pb->isVisible()) { + if (pb->isEnabled()) + pb->click(); + return; + } + } + } + break; + case Qt::Key_Escape: + reject(); + break; + default: + e->ignore(); + return; + } + } else { + e->ignore(); + } +} + +/*! \reimp */ +void QDialog::closeEvent(QCloseEvent *e) +{ +#ifndef QT_NO_WHATSTHIS + if (isModal() && QWhatsThis::inWhatsThisMode()) + QWhatsThis::leaveWhatsThisMode(); +#endif + if (isVisible()) { + QPointer<QObject> that = this; + reject(); + if (that && isVisible()) + e->ignore(); + } else { + e->accept(); + } +} + +/***************************************************************************** + Geometry management. + *****************************************************************************/ + +/*! \reimp +*/ + +void QDialog::setVisible(bool visible) +{ + Q_D(QDialog); + if (visible) { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + + if (!testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } + QWidget::setVisible(visible); + showExtension(d->doShowExtension); + QWidget *fw = window()->focusWidget(); + if (!fw) + fw = this; + + /* + The following block is to handle a special case, and does not + really follow propper logic in concern of autoDefault and TAB + order. However, it's here to ease usage for the users. If a + dialog has a default QPushButton, and first widget in the TAB + order also is a QPushButton, then we give focus to the main + default QPushButton. This simplifies code for the developers, + and actually catches most cases... If not, then they simply + have to use [widget*]->setFocus() themselves... + */ + if (d->mainDef && fw->focusPolicy() == Qt::NoFocus) { + QWidget *first = fw; + while ((first = first->nextInFocusChain()) != fw && first->focusPolicy() == Qt::NoFocus) + ; + if (first != d->mainDef && qobject_cast<QPushButton*>(first)) + d->mainDef->setFocus(); + } + if (!d->mainDef && isWindow()) { + QWidget *w = fw; + while ((w = w->nextInFocusChain()) != fw) { + QPushButton *pb = qobject_cast<QPushButton *>(w); + if (pb && pb->autoDefault() && pb->focusPolicy() != Qt::NoFocus) { + pb->setDefault(true); + break; + } + } + } + if (fw && !fw->hasFocus()) { + QFocusEvent e(QEvent::FocusIn, Qt::TabFocusReason); + QApplication::sendEvent(fw, &e); + } + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DialogStart); +#endif + + } else { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + +#ifndef QT_NO_ACCESSIBILITY + if (isVisible()) + QAccessible::updateAccessibility(this, 0, QAccessible::DialogEnd); +#endif + + // Reimplemented to exit a modal event loop when the dialog is hidden. + QWidget::setVisible(visible); + if (d->eventLoop) + d->eventLoop->exit(); + } +#ifdef Q_WS_WIN + if (d->mainDef && isActiveWindow()) { + BOOL snapToDefault = false; + if (SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapToDefault, 0)) { + if (snapToDefault) + QCursor::setPos(d->mainDef->mapToGlobal(d->mainDef->rect().center())); + } + } +#endif +} + +/*!\reimp */ +void QDialog::showEvent(QShowEvent *event) +{ + if (!event->spontaneous() && !testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } +} + +/*! \internal */ +void QDialog::adjustPosition(QWidget* w) +{ +#ifdef Q_WS_X11 + // if the WM advertises that it will place the windows properly for us, let it do it :) + if (X11->isSupportedByWM(ATOM(_NET_WM_FULL_PLACEMENT))) + return; +#endif + +#ifdef Q_OS_SYMBIAN + if (symbianAdjustedPosition()) + //dialog has already been positioned + return; +#endif + + QPoint p(0, 0); + int extraw = 0, extrah = 0, scrn = 0; + if (w) + w = w->window(); + QRect desk; + if (w) { + scrn = QApplication::desktop()->screenNumber(w); + } else if (QApplication::desktop()->isVirtualDesktop()) { + scrn = QApplication::desktop()->screenNumber(QCursor::pos()); + } else { + scrn = QApplication::desktop()->screenNumber(this); + } + desk = QApplication::desktop()->availableGeometry(scrn); + + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; (extraw == 0 || extrah == 0) && i < list.size(); ++i) { + QWidget * current = list.at(i); + if (current->isVisible()) { + int framew = current->geometry().x() - current->x(); + int frameh = current->geometry().y() - current->y(); + + extraw = qMax(extraw, framew); + extrah = qMax(extrah, frameh); + } + } + + // sanity check for decoration frames. With embedding, we + // might get extraordinary values + if (extraw == 0 || extrah == 0 || extraw >= 10 || extrah >= 40) { + extrah = 40; + extraw = 10; + } + + + if (w) { + // Use mapToGlobal rather than geometry() in case w might + // be embedded in another application + QPoint pp = w->mapToGlobal(QPoint(0,0)); + p = QPoint(pp.x() + w->width()/2, + pp.y() + w->height()/ 2); + } else { + // p = middle of the desktop + p = QPoint(desk.x() + desk.width()/2, desk.y() + desk.height()/2); + } + + // p = origin of this + p = QPoint(p.x()-width()/2 - extraw, + p.y()-height()/2 - extrah); + + + if (p.x() + extraw + width() > desk.x() + desk.width()) + p.setX(desk.x() + desk.width() - width() - extraw); + if (p.x() < desk.x()) + p.setX(desk.x()); + + if (p.y() + extrah + height() > desk.y() + desk.height()) + p.setY(desk.y() + desk.height() - height() - extrah); + if (p.y() < desk.y()) + p.setY(desk.y()); + + move(p); +} + +#if defined(Q_OS_SYMBIAN) +/*! \internal */ +bool QDialog::symbianAdjustedPosition() +{ +#if defined(Q_WS_S60) + QPoint p; + QPoint oldPos = pos(); + if (isFullScreen()) { + p.setX(0); + p.setY(0); + } else if (isMaximized()) { + TRect statusPaneRect = TRect(); + if (S60->screenHeightInPixels > S60->screenWidthInPixels) { + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStatusPane, statusPaneRect); + } else { + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStaconTop, statusPaneRect); + } + + p.setX(0); + p.setY(statusPaneRect.Height()); + } else { + // naive way to deduce screen orientation + if (S60->screenHeightInPixels > S60->screenWidthInPixels) { + int cbaHeight; + TRect rect; + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EControlPane, rect); + cbaHeight = rect.Height(); + p.setY(S60->screenHeightInPixels - height() - cbaHeight); + p.setX(0); + } else { + const int scrollbarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + TRect staConTopRect = TRect(); + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStaconTop, staConTopRect); + if (staConTopRect.IsEmpty()) { + TRect cbaRect = TRect(); + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EControlPane, cbaRect); + AknLayoutUtils::TAknCbaLocation cbaLocation = AknLayoutUtils::CbaLocation(); + switch (cbaLocation) { + case AknLayoutUtils::EAknCbaLocationBottom: + p.setY(S60->screenHeightInPixels - height() - cbaRect.Height()); + p.setX((S60->screenWidthInPixels - width()) >> 1); + break; + case AknLayoutUtils::EAknCbaLocationRight: + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,S60->screenWidthInPixels - width() - scrollbarWidth - cbaRect.Width())); + break; + case AknLayoutUtils::EAknCbaLocationLeft: + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,scrollbarWidth + cbaRect.Width())); + break; + } + } else { + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,S60->screenWidthInPixels - width())); + } + } + } + if (oldPos != p || p.y() < 0) + move(p); + return true; +#else + // TODO - check positioning requirement for Symbian, non-s60 + return false; +#endif +} +#endif + +/*! + \obsolete + + If \a orientation is Qt::Horizontal, the extension will be displayed + to the right of the dialog's main area. If \a orientation is + Qt::Vertical, the extension will be displayed below the dialog's main + area. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa setExtension() +*/ +void QDialog::setOrientation(Qt::Orientation orientation) +{ + Q_D(QDialog); + d->orientation = orientation; +} + +/*! + \obsolete + + Returns the dialog's extension orientation. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa extension() +*/ +Qt::Orientation QDialog::orientation() const +{ + Q_D(const QDialog); + return d->orientation; +} + +/*! + \obsolete + + Sets the widget, \a extension, to be the dialog's extension, + deleting any previous extension. The dialog takes ownership of the + extension. Note that if 0 is passed any existing extension will be + deleted. This function must only be called while the dialog is hidden. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa showExtension(), setOrientation() +*/ +void QDialog::setExtension(QWidget* extension) +{ + Q_D(QDialog); + delete d->extension; + d->extension = extension; + + if (!extension) + return; + + if (extension->parentWidget() != this) + extension->setParent(this); + extension->hide(); +} + +/*! + \obsolete + + Returns the dialog's extension or 0 if no extension has been + defined. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa showExtension(), setOrientation() +*/ +QWidget* QDialog::extension() const +{ + Q_D(const QDialog); + return d->extension; +} + + +/*! + \obsolete + + If \a showIt is true, the dialog's extension is shown; otherwise the + extension is hidden. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa show(), setExtension(), setOrientation() +*/ +void QDialog::showExtension(bool showIt) +{ + Q_D(QDialog); + d->doShowExtension = showIt; + if (!d->extension) + return; + if (!testAttribute(Qt::WA_WState_Visible)) + return; + if (d->extension->isVisible() == showIt) + return; + + if (showIt) { + d->size = size(); + d->min = minimumSize(); + d->max = maximumSize(); + if (layout()) + layout()->setEnabled(false); + QSize s(d->extension->sizeHint() + .expandedTo(d->extension->minimumSize()) + .boundedTo(d->extension->maximumSize())); + if (d->orientation == Qt::Horizontal) { + int h = qMax(height(), s.height()); + d->extension->setGeometry(width(), 0, s.width(), h); + setFixedSize(width() + s.width(), h); + } else { + int w = qMax(width(), s.width()); + d->extension->setGeometry(0, height(), w, s.height()); + setFixedSize(w, height() + s.height()); + } + d->extension->show(); +#ifndef QT_NO_SIZEGRIP + const bool sizeGripEnabled = isSizeGripEnabled(); + setSizeGripEnabled(false); + d->sizeGripEnabled = sizeGripEnabled; +#endif + } else { + d->extension->hide(); + // workaround for CDE window manager that won't shrink with (-1,-1) + setMinimumSize(d->min.expandedTo(QSize(1, 1))); + setMaximumSize(d->max); + resize(d->size); + if (layout()) + layout()->setEnabled(true); +#ifndef QT_NO_SIZEGRIP + setSizeGripEnabled(d->sizeGripEnabled); +#endif + } +} + + +/*! \reimp */ +QSize QDialog::sizeHint() const +{ + Q_D(const QDialog); + if (d->extension) { + if (d->orientation == Qt::Horizontal) + return QSize(QWidget::sizeHint().width(), + qMax(QWidget::sizeHint().height(),d->extension->sizeHint().height())); + else + return QSize(qMax(QWidget::sizeHint().width(), d->extension->sizeHint().width()), + QWidget::sizeHint().height()); + } +#if defined(Q_WS_S60) + // if size is not fixed, try to adjust it according to S60 layoutting + if (minimumSize() != maximumSize()) { + // In S60, dialogs are always the width of screen (in portrait, regardless of current layout) + return QSize(qMin(S60->screenHeightInPixels, S60->screenWidthInPixels), QWidget::sizeHint().height()); + } else { + return QWidget::sizeHint(); + } +#else + return QWidget::sizeHint(); +#endif //Q_WS_S60 +} + + +/*! \reimp */ +QSize QDialog::minimumSizeHint() const +{ + Q_D(const QDialog); + if (d->extension) { + if (d->orientation == Qt::Horizontal) + return QSize(QWidget::minimumSizeHint().width(), + qMax(QWidget::minimumSizeHint().height(), d->extension->minimumSizeHint().height())); + else + return QSize(qMax(QWidget::minimumSizeHint().width(), d->extension->minimumSizeHint().width()), + QWidget::minimumSizeHint().height()); + } + + return QWidget::minimumSizeHint(); +} + +/*! + \property QDialog::modal + \brief whether show() should pop up the dialog as modal or modeless + + By default, this property is false and show() pops up the dialog + as modeless. Setting his property to true is equivalent to setting + QWidget::windowModality to Qt::ApplicationModal. + + exec() ignores the value of this property and always pops up the + dialog as modal. + + \sa QWidget::windowModality, show(), exec() +*/ + +void QDialog::setModal(bool modal) +{ + setAttribute(Qt::WA_ShowModal, modal); +} + + +bool QDialog::isSizeGripEnabled() const +{ +#ifndef QT_NO_SIZEGRIP + Q_D(const QDialog); + return !!d->resizer; +#else + return false; +#endif +} + + +void QDialog::setSizeGripEnabled(bool enabled) +{ +#ifdef QT_NO_SIZEGRIP + Q_UNUSED(enabled); +#else + Q_D(QDialog); +#ifndef QT_NO_SIZEGRIP + d->sizeGripEnabled = enabled; + if (enabled && d->doShowExtension) + return; +#endif + if (!enabled != !d->resizer) { + if (enabled) { + d->resizer = new QSizeGrip(this); + // adjustSize() processes all events, which is suboptimal + d->resizer->resize(d->resizer->sizeHint()); + if (isRightToLeft()) + d->resizer->move(rect().bottomLeft() -d->resizer->rect().bottomLeft()); + else + d->resizer->move(rect().bottomRight() -d->resizer->rect().bottomRight()); + d->resizer->raise(); + d->resizer->show(); + } else { + delete d->resizer; + d->resizer = 0; + } + } +#endif //QT_NO_SIZEGRIP +} + + + +/*! \reimp */ +void QDialog::resizeEvent(QResizeEvent *) +{ +#ifndef QT_NO_SIZEGRIP + Q_D(QDialog); + if (d->resizer) { + if (isRightToLeft()) + d->resizer->move(rect().bottomLeft() -d->resizer->rect().bottomLeft()); + else + d->resizer->move(rect().bottomRight() -d->resizer->rect().bottomRight()); + d->resizer->raise(); + } +#endif +} + +/*! \fn void QDialog::finished(int result) + \since 4.1 + + This signal is emitted when the dialog's \a result code has been + set, either by the user or by calling done(), accept(), or + reject(). + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa accepted(), rejected() +*/ + +/*! \fn void QDialog::accepted() + \since 4.1 + + This signal is emitted when the dialog has been accepted either by + the user or by calling accept() or done() with the + QDialog::Accepted argument. + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa finished(), rejected() +*/ + +/*! \fn void QDialog::rejected() + \since 4.1 + + This signal is emitted when the dialog has been rejected either by + the user or by calling reject() or done() with the + QDialog::Rejected argument. + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa finished(), accepted() +*/ + +QT_END_NAMESPACE +#include "moc_qdialog.cpp" diff --git a/src/widgets/dialogs/qdialog.h b/src/widgets/dialogs/qdialog.h new file mode 100644 index 0000000000..ce8aa91bc7 --- /dev/null +++ b/src/widgets/dialogs/qdialog.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIALOG_H +#define QDIALOG_H + +#include <QtWidgets/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPushButton; +class QDialogPrivate; + +class Q_WIDGETS_EXPORT QDialog : public QWidget +{ + Q_OBJECT + friend class QPushButton; + + Q_PROPERTY(bool sizeGripEnabled READ isSizeGripEnabled WRITE setSizeGripEnabled) + Q_PROPERTY(bool modal READ isModal WRITE setModal) + +public: + explicit QDialog(QWidget *parent = 0, Qt::WindowFlags f = 0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QDialog(QWidget *parent, const char *name, bool modal = false, + Qt::WindowFlags f = 0); +#endif + ~QDialog(); + + enum DialogCode { Rejected, Accepted }; + + int result() const; + + void setVisible(bool visible); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void setExtension(QWidget* extension); + QWidget* extension() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setSizeGripEnabled(bool); + bool isSizeGripEnabled() const; + + void setModal(bool modal); + void setResult(int r); + +Q_SIGNALS: + void finished(int result); + void accepted(); + void rejected(); + +public Q_SLOTS: + void open(); + int exec(); + virtual void done(int); + virtual void accept(); + virtual void reject(); + + void showExtension(bool); + +protected: + QDialog(QDialogPrivate &, QWidget *parent, Qt::WindowFlags f = 0); + +#if defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) + bool event(QEvent *e); +#endif + void keyPressEvent(QKeyEvent *); + void closeEvent(QCloseEvent *); + void showEvent(QShowEvent *); + void resizeEvent(QResizeEvent *); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif + bool eventFilter(QObject *, QEvent *); + void adjustPosition(QWidget*); +private: + Q_DECLARE_PRIVATE(QDialog) + Q_DISABLE_COPY(QDialog) + +#if defined(Q_OS_SYMBIAN) + bool symbianAdjustedPosition(); +#endif + + +#ifdef Q_WS_WINCE_WM + Q_PRIVATE_SLOT(d_func(), void _q_doneAction()) +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIALOG_H diff --git a/src/widgets/dialogs/qdialog_p.h b/src/widgets/dialogs/qdialog_p.h new file mode 100644 index 0000000000..3ee88f4703 --- /dev/null +++ b/src/widgets/dialogs/qdialog_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIALOG_P_H +#define QDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qwidget_p.h" +#include "QtCore/qeventloop.h" +#include "QtCore/qpointer.h" +#include "QtWidgets/qdialog.h" +#include "QtWidgets/qpushbutton.h" + +QT_BEGIN_NAMESPACE + +class QSizeGrip; + +class QDialogPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QDialog) +public: + + QDialogPrivate() + : mainDef(0), orientation(Qt::Horizontal),extension(0), doShowExtension(false), +#ifndef QT_NO_SIZEGRIP + resizer(0), + sizeGripEnabled(false), +#endif + rescode(0), resetModalityTo(-1), wasModalitySet(true), eventLoop(0) + {} + + QPointer<QPushButton> mainDef; + Qt::Orientation orientation; + QWidget *extension; + bool doShowExtension; + QSize size, min, max; +#ifndef QT_NO_SIZEGRIP + QSizeGrip *resizer; + bool sizeGripEnabled; +#endif + QPoint lastRMBPress; + + void setDefault(QPushButton *); + void setMainDefault(QPushButton *); + void hideDefault(); + void resetModalitySetByOpen(); + +#ifdef Q_WS_WINCE_WM + void _q_doneAction(); +#endif + +#ifdef Q_WS_MAC + virtual void mac_nativeDialogModalHelp() {} +#endif + + int rescode; + int resetModalityTo; + bool wasModalitySet; + + QPointer<QEventLoop> eventLoop; +}; + +QT_END_NAMESPACE + +#endif // QDIALOG_P_H diff --git a/src/widgets/dialogs/qdialogsbinarycompat_win.cpp b/src/widgets/dialogs/qdialogsbinarycompat_win.cpp new file mode 100644 index 0000000000..09c96eaafd --- /dev/null +++ b/src/widgets/dialogs/qdialogsbinarycompat_win.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> + +// ### Qt 5: eliminate this file + +/* + This is evil. MSVC doesn't let us remove private symbols, nor change their + visibility; yet there are some symbols we really needed to make public, e.g., + ~QColorDialog(), and then there were some totally needless symbols in our + header files, e.g., setSelectedAlpha(). So we define a new version of + QColorDialog & Co. with only the private symbols that we removed from the + public header files. The friends are there only to prevent potential compiler + warnings. + + It would have been nicer to export the missing symbols as mangled "C" symbols + instead but unfortunately MSVC uses out-of-reach characters like @ and . in + their mangled C++ symbols. +*/ + +#if QT_VERSION < 0x050000 && defined(Q_CC_MSVC) + +QT_BEGIN_NAMESPACE + +#include <QtGui/QColor> +#include <QtGui/QFont> + +class QColorDialogPrivate; +class QFontDialogPrivate; +class QInputDialogPrivate; +class QWidget; + +class Q_WIDGETS_EXPORT QColorDialog +{ +private: + explicit QColorDialog(QWidget *, bool); + ~QColorDialog(); + + void setColor(const QColor &); + QColor color() const; + bool selectColor(const QColor &); + void setSelectedAlpha(int); + int selectedAlpha() const; + + friend class QColorDialogPrivate; +}; + +QColorDialog::QColorDialog(QWidget *, bool) {} +QColorDialog::~QColorDialog() {} +void QColorDialog::setColor(const QColor &) {} +QColor QColorDialog::color() const { return QColor(); } +bool QColorDialog::selectColor(const QColor &) { return false; } +void QColorDialog::setSelectedAlpha(int) {} +int QColorDialog::selectedAlpha() const { return 0; } + +class Q_WIDGETS_EXPORT QFontDialog +{ +private: + explicit QFontDialog(QWidget *, bool, Qt::WindowFlags); + ~QFontDialog(); + + QFont font() const; + void setFont(const QFont &); + void updateFamilies(); + void updateStyles(); + void updateSizes(); + + static QFont getFont(bool *, const QFont *, QWidget *); + + friend class QFontDialogPrivate; +}; + +QFontDialog::QFontDialog(QWidget *, bool, Qt::WindowFlags) {} +QFontDialog::~QFontDialog() {} +QFont QFontDialog::font() const { return QFont(); } +void QFontDialog::setFont(const QFont &) { } +void QFontDialog::updateFamilies() {} +void QFontDialog::updateStyles() {} +void QFontDialog::updateSizes() {} +QFont QFontDialog::getFont(bool *, const QFont *, QWidget *) { return QFont(); } + +class Q_WIDGETS_EXPORT QInputDialog +{ +private: + enum Type { LineEdit, SpinBox, DoubleSpinBox, ComboBox, EditableComboBox }; + + QInputDialog(const QString &, QWidget *, Type, Qt::WindowFlags); + QInputDialog(const QString &, const QString &, QWidget *, QWidget *, Qt::WindowFlags); + ~QInputDialog(); +}; + +QInputDialog::QInputDialog(const QString &, QWidget *, Type, Qt::WindowFlags) {} +QInputDialog::QInputDialog(const QString &, const QString &, QWidget *, QWidget *, Qt::WindowFlags) {} +QInputDialog::~QInputDialog() {} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qerrormessage.cpp b/src/widgets/dialogs/qerrormessage.cpp new file mode 100644 index 0000000000..890e6ca6b7 --- /dev/null +++ b/src/widgets/dialogs/qerrormessage.cpp @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qerrormessage.h" + +#ifndef QT_NO_ERRORMESSAGE + +#include "qapplication.h" +#include "qcheckbox.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qmessagebox.h" +#include "qpushbutton.h" +#include "qstringlist.h" +#include "qtextedit.h" +#include "qdialog_p.h" +#include "qpixmap.h" +#include "qmetaobject.h" +#include "qthread.h" +#include "qqueue.h" +#include "qset.h" + +#include <stdio.h> +#include <stdlib.h> + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_high_dpi(); //defined in qguifunctions_wince.cpp + +#include "qguifunctions_wince.h" +#endif + +#if defined(QT_SOFTKEYS_ENABLED) +#include <qaction.h> +#endif +#ifdef Q_WS_S60 +#include "private/qt_s60_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QErrorMessagePrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QErrorMessage) +public: + QPushButton * ok; + QCheckBox * again; + QTextEdit * errors; + QLabel * icon; +#ifdef QT_SOFTKEYS_ENABLED + QAction *okAction; +#endif + QQueue<QPair<QString, QString> > pending; + QSet<QString> doNotShow; + QSet<QString> doNotShowType; + QString currentMessage; + QString currentType; + + bool nextPending(); + void retranslateStrings(); +}; + +class QErrorMessageTextView : public QTextEdit +{ +public: + QErrorMessageTextView(QWidget *parent) + : QTextEdit(parent) { setReadOnly(true); } + + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; +}; + +QSize QErrorMessageTextView::minimumSizeHint() const +{ +#ifdef Q_WS_WINCE + if (qt_wince_is_mobile()) + if (qt_wince_is_high_dpi()) + return QSize(200, 200); + else + return QSize(100, 100); + else + return QSize(70, 70); +#else + return QSize(50, 50); +#endif +} + +QSize QErrorMessageTextView::sizeHint() const +{ +#ifdef Q_WS_WINCE + if (qt_wince_is_mobile()) + if (qt_wince_is_high_dpi()) + return QSize(400, 200); + else + return QSize(320, 120); + else + return QSize(300, 100); +#else + +#ifdef Q_WS_S60 + const int smallerDimension = qMin(S60->screenHeightInPixels, S60->screenWidthInPixels); + // In S60 layout data, error messages seem to be one third of the screen height (in portrait) minus two. + return QSize(smallerDimension, smallerDimension/3-2); +#else + return QSize(250, 75); +#endif //Q_WS_S60 +#endif //Q_WS_WINCE +} + +/*! + \class QErrorMessage + + \brief The QErrorMessage class provides an error message display dialog. + + \ingroup standard-dialog + + An error message widget consists of a text label and a checkbox. The + checkbox lets the user control whether the same error message will be + displayed again in the future, typically displaying the text, + "Show this message again" translated into the appropriate local + language. + + For production applications, the class can be used to display messages which + the user only needs to see once. To use QErrorMessage like this, you create + the dialog in the usual way, and show it by calling the showMessage() slot or + connecting signals to it. + + The static qtHandler() function installs a message handler + using qInstallMsgHandler() and creates a QErrorMessage that displays + qDebug(), qWarning() and qFatal() messages. This is most useful in + environments where no console is available to display warnings and + error messages. + + In both cases QErrorMessage will queue pending messages and display + them in order, with each new message being shown as soon as the user + has accepted the previous message. Once the user has specified that a + message is not to be shown again it is automatically skipped, and the + dialog will show the next appropriate message in the queue. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QErrorMessage as well as other built-in Qt dialogs. + + \img qerrormessage.png + + \sa QMessageBox, QStatusBar::showMessage(), {Standard Dialogs Example} +*/ + +static QErrorMessage * qtMessageHandler = 0; + +static void deleteStaticcQErrorMessage() // post-routine +{ + if (qtMessageHandler) { + delete qtMessageHandler; + qtMessageHandler = 0; + } +} + +static bool metFatal = false; + +static void jump(QtMsgType t, const char * m) +{ + if (!qtMessageHandler) + return; + + QString rich; + + switch (t) { + case QtDebugMsg: + default: + rich = QErrorMessage::tr("Debug Message:"); + break; + case QtWarningMsg: + rich = QErrorMessage::tr("Warning:"); + break; + case QtFatalMsg: + rich = QErrorMessage::tr("Fatal Error:"); + } + rich = QString::fromLatin1("<p><b>%1</b></p>").arg(rich); + rich += Qt::convertFromPlainText(QLatin1String(m), Qt::WhiteSpaceNormal); + + // ### work around text engine quirk + if (rich.endsWith(QLatin1String("</p>"))) + rich.chop(4); + + if (!metFatal) { + if (QThread::currentThread() == qApp->thread()) { + qtMessageHandler->showMessage(rich); + } else { + QMetaObject::invokeMethod(qtMessageHandler, + "showMessage", + Qt::QueuedConnection, + Q_ARG(QString, rich)); + } + metFatal = (t == QtFatalMsg); + } +} + + +/*! + Constructs and installs an error handler window with the given \a + parent. +*/ + +QErrorMessage::QErrorMessage(QWidget * parent) + : QDialog(*new QErrorMessagePrivate, parent) +{ + Q_D(QErrorMessage); + QGridLayout * grid = new QGridLayout(this); + d->icon = new QLabel(this); +#ifndef QT_NO_MESSAGEBOX + d->icon->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); + d->icon->setAlignment(Qt::AlignHCenter | Qt::AlignTop); +#endif + grid->addWidget(d->icon, 0, 0, Qt::AlignTop); + d->errors = new QErrorMessageTextView(this); + grid->addWidget(d->errors, 0, 1); + d->again = new QCheckBox(this); + d->again->setChecked(true); + grid->addWidget(d->again, 1, 1, Qt::AlignTop); + d->ok = new QPushButton(this); +#ifdef QT_SOFTKEYS_ENABLED + d->okAction = new QAction(d->ok); + d->okAction->setSoftKeyRole(QAction::PositiveSoftKey); + connect(d->okAction, SIGNAL(triggered()), this, SLOT(accept())); + addAction(d->okAction); +#endif + + +#if defined(Q_WS_WINCE) || defined(Q_WS_S60) + d->ok->setFixedSize(0,0); +#endif + connect(d->ok, SIGNAL(clicked()), this, SLOT(accept())); + d->ok->setFocus(); + grid->addWidget(d->ok, 2, 0, 1, 2, Qt::AlignCenter); + grid->setColumnStretch(1, 42); + grid->setRowStretch(0, 42); + d->retranslateStrings(); +} + + +/*! + Destroys the error message dialog. +*/ + +QErrorMessage::~QErrorMessage() +{ + if (this == qtMessageHandler) { + qtMessageHandler = 0; + QtMsgHandler tmp = qInstallMsgHandler(0); + // in case someone else has later stuck in another... + if (tmp != jump) + qInstallMsgHandler(tmp); + } +} + + +/*! \reimp */ + +void QErrorMessage::done(int a) +{ + Q_D(QErrorMessage); + if (!d->again->isChecked() && !d->currentMessage.isEmpty() && d->currentType.isEmpty()) { + d->doNotShow.insert(d->currentMessage); + } + if (!d->again->isChecked() && !d->currentType.isEmpty()) { + d->doNotShowType.insert(d->currentType); + } + d->currentMessage.clear(); + d->currentType.clear(); + if (!d->nextPending()) { + QDialog::done(a); + if (this == qtMessageHandler && metFatal) + exit(1); + } +} + + +/*! + Returns a pointer to a QErrorMessage object that outputs the + default Qt messages. This function creates such an object, if there + isn't one already. +*/ + +QErrorMessage * QErrorMessage::qtHandler() +{ + if (!qtMessageHandler) { + qtMessageHandler = new QErrorMessage(0); + qAddPostRoutine(deleteStaticcQErrorMessage); // clean up + qtMessageHandler->setWindowTitle(QApplication::applicationName()); + qInstallMsgHandler(jump); + } + return qtMessageHandler; +} + + +/*! \internal */ + +bool QErrorMessagePrivate::nextPending() +{ + while (!pending.isEmpty()) { + QPair<QString,QString> pendingMessage = pending.dequeue(); + QString message = pendingMessage.first; + QString type = pendingMessage.second; + if (!message.isEmpty() && ((type.isEmpty() && !doNotShow.contains(message)) || (!type.isEmpty() && !doNotShowType.contains(type)))) { +#ifndef QT_NO_TEXTHTMLPARSER + errors->setHtml(message); +#else + errors->setPlainText(message); +#endif + currentMessage = message; + currentType = type; + return true; + } + } + return false; +} + + +/*! + Shows the given message, \a message, and returns immediately. If the user + has requested for the message not to be shown again, this function does + nothing. + + Normally, the message is displayed immediately. However, if there are + pending messages, it will be queued to be displayed later. +*/ + +void QErrorMessage::showMessage(const QString &message) +{ + Q_D(QErrorMessage); + if (d->doNotShow.contains(message)) + return; + d->pending.enqueue(qMakePair(message,QString())); + if (!isVisible() && d->nextPending()) + show(); +} + +/*! + \since 4.5 + \overload + + Shows the given message, \a message, and returns immediately. If the user + has requested for messages of type, \a type, not to be shown again, this + function does nothing. + + Normally, the message is displayed immediately. However, if there are + pending messages, it will be queued to be displayed later. + + \sa showMessage() +*/ + +void QErrorMessage::showMessage(const QString &message, const QString &type) +{ + Q_D(QErrorMessage); + if (d->doNotShow.contains(message) && d->doNotShowType.contains(type)) + return; + d->pending.push_back(qMakePair(message,type)); + if (!isVisible() && d->nextPending()) + show(); +} + +/*! + \reimp +*/ +void QErrorMessage::changeEvent(QEvent *e) +{ + Q_D(QErrorMessage); + if (e->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +void QErrorMessagePrivate::retranslateStrings() +{ + again->setText(QErrorMessage::tr("&Show this message again")); + ok->setText(QErrorMessage::tr("&OK")); +#ifdef QT_SOFTKEYS_ENABLED + okAction->setText(ok->text()); +#endif +} + +/*! + \fn void QErrorMessage::message(const QString & message) + + Use showMessage(\a message) instead. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_ERRORMESSAGE diff --git a/src/widgets/dialogs/qerrormessage.h b/src/widgets/dialogs/qerrormessage.h new file mode 100644 index 0000000000..a8805a2372 --- /dev/null +++ b/src/widgets/dialogs/qerrormessage.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QERRORMESSAGE_H +#define QERRORMESSAGE_H + +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ERRORMESSAGE + +class QErrorMessagePrivate; + +class Q_WIDGETS_EXPORT QErrorMessage: public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QErrorMessage) +public: + explicit QErrorMessage(QWidget* parent = 0); + ~QErrorMessage(); + + static QErrorMessage * qtHandler(); + +public Q_SLOTS: + void showMessage(const QString &message); + void showMessage(const QString &message, const QString &type); +#ifdef QT3_SUPPORT + inline QT_MOC_COMPAT void message(const QString &text) { showMessage(text); } +#endif + +protected: + void done(int); + void changeEvent(QEvent *e); + +private: + Q_DISABLE_COPY(QErrorMessage) +}; + +#endif // QT_NO_ERRORMESSAGE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QERRORMESSAGE_H diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp new file mode 100644 index 0000000000..9d6e348b0d --- /dev/null +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -0,0 +1,3555 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qvariant.h> +#include <private/qwidgetitemdata_p.h> +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG +#include "qfiledialog_p.h" +#include <qfontmetrics.h> +#include <qaction.h> +#include <qheaderview.h> +#include <qshortcut.h> +#include <qgridlayout.h> +#include <qmenu.h> +#include <qmessagebox.h> +#include <qinputdialog.h> +#include <stdlib.h> +#include <qsettings.h> +#include <qdebug.h> +#include <qapplication.h> +#include <qstylepainter.h> +#if !defined(Q_WS_WINCE) && !defined(Q_OS_SYMBIAN) +#include "ui_qfiledialog.h" +#else +#define Q_EMBEDDED_SMALLSCREEN +#include "ui_qfiledialog_embedded.h" +#if defined(Q_OS_WINCE) +extern bool qt_priv_ptr_valid; +#endif +#if defined(Q_OS_UNIX) +#include <pwd.h> +#endif +#endif + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QString, lastVisitedDir) + +/* + \internal + + Exported hooks that can be used to customize the static functions. + */ +typedef QString (*_qt_filedialog_existing_directory_hook)(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options); +Q_WIDGETS_EXPORT _qt_filedialog_existing_directory_hook qt_filedialog_existing_directory_hook = 0; + +typedef QString (*_qt_filedialog_open_filename_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_WIDGETS_EXPORT _qt_filedialog_open_filename_hook qt_filedialog_open_filename_hook = 0; + +typedef QStringList (*_qt_filedialog_open_filenames_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_WIDGETS_EXPORT _qt_filedialog_open_filenames_hook qt_filedialog_open_filenames_hook = 0; + +typedef QString (*_qt_filedialog_save_filename_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_WIDGETS_EXPORT _qt_filedialog_save_filename_hook qt_filedialog_save_filename_hook = 0; + +/*! + \class QFileDialog + \brief The QFileDialog class provides a dialog that allow users to select files or directories. + \ingroup standard-dialogs + + + The QFileDialog class enables a user to traverse the file system in + order to select one or many files or a directory. + + The easiest way to create a QFileDialog is to use the static + functions. On Windows, Mac OS X, KDE and GNOME, these static functions will + call the native file dialog when possible. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 0 + + In the above example, a modal QFileDialog is created using a static + function. The dialog initially displays the contents of the "/home/jana" + directory, and displays files matching the patterns given in the + string "Image Files (*.png *.jpg *.bmp)". The parent of the file dialog + is set to \e this, and the window title is set to "Open Image". + + If you want to use multiple filters, separate each one with + \e two semicolons. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 1 + + You can create your own QFileDialog without using the static + functions. By calling setFileMode(), you can specify what the user must + select in the dialog: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 2 + + In the above example, the mode of the file dialog is set to + AnyFile, meaning that the user can select any file, or even specify a + file that doesn't exist. This mode is useful for creating a + "Save As" file dialog. Use ExistingFile if the user must select an + existing file, or \l Directory if only a directory may be selected. + See the \l QFileDialog::FileMode enum for the complete list of modes. + + The fileMode property contains the mode of operation for the dialog; + this indicates what types of objects the user is expected to select. + Use setNameFilter() to set the dialog's file filter. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 3 + + In the above example, the filter is set to \c{"Images (*.png *.xpm *.jpg)"}, + this means that only files with the extension \c png, \c xpm, + or \c jpg will be shown in the QFileDialog. You can apply + several filters by using setNameFilters(). Use selectNameFilter() to select + one of the filters you've given as the file dialog's default filter. + + The file dialog has two view modes: \l{QFileDialog::}{List} and + \l{QFileDialog::}{Detail}. + \l{QFileDialog::}{List} presents the contents of the current directory + as a list of file and directory names. \l{QFileDialog::}{Detail} also + displays a list of file and directory names, but provides additional + information alongside each name, such as the file size and modification + date. Set the mode with setViewMode(): + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 4 + + The last important function you will need to use when creating your + own file dialog is selectedFiles(). + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 5 + + In the above example, a modal file dialog is created and shown. If + the user clicked OK, the file they selected is put in \c fileName. + + The dialog's working directory can be set with setDirectory(). + Each file in the current directory can be selected using + the selectFile() function. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QFileDialog as well as other built-in Qt dialogs. + + \sa QDir, QFileInfo, QFile, QPrintDialog, QColorDialog, QFontDialog, {Standard Dialogs Example}, + {Application Example} +*/ + +/*! + \enum QFileDialog::AcceptMode + + \value AcceptOpen + \value AcceptSave +*/ + +/*! + \enum QFileDialog::ViewMode + + This enum describes the view mode of the file dialog; i.e. what + information about each file will be displayed. + + \value Detail Displays an icon, a name, and details for each item in + the directory. + \value List Displays only an icon and a name for each item in the + directory. + + \sa setViewMode() +*/ + +/*! + \enum QFileDialog::FileMode + + This enum is used to indicate what the user may select in the file + dialog; i.e. what the dialog will return if the user clicks OK. + + \value AnyFile The name of a file, whether it exists or not. + \value ExistingFile The name of a single existing file. + \value Directory The name of a directory. Both files and + directories are displayed. + \value ExistingFiles The names of zero or more existing files. + + This value is obsolete since Qt 4.5: + + \value DirectoryOnly Use \c Directory and setOption(ShowDirsOnly, true) instead. + + \sa setFileMode() +*/ + +/*! + \enum QFileDialog::Option + + \value ShowDirsOnly Only show directories in the file dialog. By + default both files and directories are shown. (Valid only in the + \l Directory file mode.) + + \value DontResolveSymlinks Don't resolve symlinks in the file + dialog. By default symlinks are resolved. + + \value DontConfirmOverwrite Don't ask for confirmation if an + existing file is selected. By default confirmation is requested. + + \value DontUseNativeDialog Don't use the native file dialog. By + default, the native file dialog is used unless you use a subclass + of QFileDialog that contains the Q_OBJECT macro. + + \value ReadOnly Indicates that the model is readonly. + + \value HideNameFilterDetails Indicates if the file name filter details are + hidden or not. + + \value DontUseSheet In previous versions of Qt, the static + functions would create a sheet by default if the static function + was given a parent. This is no longer supported and does nothing in Qt 4.5, The + static functions will always be an application modal dialog. If + you want to use sheets, use QFileDialog::open() instead. + +*/ + +/*! + \enum QFileDialog::DialogLabel + + \value LookIn + \value FileName + \value FileType + \value Accept + \value Reject +*/ + +/*! + \fn void QFileDialog::filesSelected(const QStringList &selected) + + When the selection changes and the dialog is accepted, this signal is + emitted with the (possibly empty) list of \a selected files. + + \sa currentChanged(), QDialog::Accepted +*/ + + +/*! + \fn void QFileDialog::fileSelected(const QString &file) + + When the selection changes and the dialog is accepted, this signal is + emitted with the (possibly empty) selected \a file. + + \sa currentChanged(), QDialog::Accepted +*/ + + +/*! + \fn void QFileDialog::currentChanged(const QString &path) + + When the current file changes, this signal is emitted with the + new file name as the \a path parameter. + + \sa filesSelected() +*/ + +/*! + \fn void QFileDialog::directoryEntered(const QString &directory) + \since 4.3 + + This signal is emitted when the user enters a \a directory. +*/ + +/*! + \fn void QFileDialog::filterSelected(const QString &filter) + \since 4.3 + + This signal is emitted when the user selects a \a filter. +*/ + +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) +bool Q_WIDGETS_EXPORT qt_use_native_dialogs = true; // for the benefit of testing tools, until we have a proper API +#endif + +QT_BEGIN_INCLUDE_NAMESPACE +#ifdef Q_WS_WIN +#include <qwindowsstyle.h> +#endif +#include <qshortcut.h> +#ifdef Q_WS_MAC +#include <qmacstyle_mac.h> +#endif +QT_END_INCLUDE_NAMESPACE + +/*! + \fn QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags flags) + + Constructs a file dialog with the given \a parent and widget \a flags. +*/ +QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(*new QFileDialogPrivate, parent, f) +{ + Q_D(QFileDialog); + d->init(); + d->lineEdit()->selectAll(); +} + +/*! + Constructs a file dialog with the given \a parent and \a caption that + initially displays the contents of the specified \a directory. + The contents of the directory are filtered before being shown in the + dialog, using a semicolon-separated list of filters specified by + \a filter. +*/ +QFileDialog::QFileDialog(QWidget *parent, + const QString &caption, + const QString &directory, + const QString &filter) + : QDialog(*new QFileDialogPrivate, parent, 0) +{ + Q_D(QFileDialog); + d->init(directory, filter, caption); + d->lineEdit()->selectAll(); +} + +/*! + \internal +*/ +QFileDialog::QFileDialog(const QFileDialogArgs &args) + : QDialog(*new QFileDialogPrivate, args.parent, 0) +{ + Q_D(QFileDialog); + d->init(args.directory, args.filter, args.caption); + setFileMode(args.mode); + setOptions(args.options); + selectFile(args.selection); + d->lineEdit()->selectAll(); +} + +/*! + Destroys the file dialog. +*/ +QFileDialog::~QFileDialog() +{ + Q_D(QFileDialog); +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + settings.setValue(QLatin1String("filedialog"), saveState()); +#endif + d->deleteNativeDialog_sys(); +} + +/*! + \since 4.3 + Sets the \a urls that are located in the sidebar. + + For instance: + + \snippet doc/src/snippets/filedialogurls.cpp 0 + + The file dialog will then look like this: + + \image filedialogurls.png + + \sa sidebarUrls() +*/ +void QFileDialog::setSidebarUrls(const QList<QUrl> &urls) +{ + Q_D(QFileDialog); + d->qFileDialogUi->sidebar->setUrls(urls); +} + +/*! + \since 4.3 + Returns a list of urls that are currently in the sidebar +*/ +QList<QUrl> QFileDialog::sidebarUrls() const +{ + Q_D(const QFileDialog); + return d->qFileDialogUi->sidebar->urls(); +} + +static const qint32 QFileDialogMagic = 0xbe; + +const char *qt_file_dialog_filter_reg_exp = +"^(.*)\\(([a-zA-Z0-9_.*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; + +/*! + \since 4.3 + Saves the state of the dialog's layout, history and current directory. + + Typically this is used in conjunction with QSettings to remember the size + for a future session. A version number is stored as part of the data. +*/ +QByteArray QFileDialog::saveState() const +{ + Q_D(const QFileDialog); + int version = 3; + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + + stream << qint32(QFileDialogMagic); + stream << qint32(version); + stream << d->qFileDialogUi->splitter->saveState(); + stream << d->qFileDialogUi->sidebar->urls(); + stream << history(); + stream << *lastVisitedDir(); + stream << d->qFileDialogUi->treeView->header()->saveState(); + stream << qint32(viewMode()); + return data; +} + +/*! + \since 4.3 + Restores the dialogs's layout, history and current directory to the \a state specified. + + Typically this is used in conjunction with QSettings to restore the size + from a past session. + + Returns false if there are errors +*/ +bool QFileDialog::restoreState(const QByteArray &state) +{ + Q_D(QFileDialog); + int version = 3; + QByteArray sd = state; + QDataStream stream(&sd, QIODevice::ReadOnly); + if (stream.atEnd()) + return false; + QByteArray splitterState; + QByteArray headerData; + QList<QUrl> bookmarks; + QStringList history; + QString currentDirectory; + qint32 marker; + qint32 v; + qint32 viewMode; + stream >> marker; + stream >> v; + if (marker != QFileDialogMagic || v != version) + return false; + + stream >> splitterState + >> bookmarks + >> history + >> currentDirectory + >> headerData + >> viewMode; + + if (!d->qFileDialogUi->splitter->restoreState(splitterState)) + return false; + QList<int> list = d->qFileDialogUi->splitter->sizes(); + if (list.count() >= 2 && list.at(0) == 0 && list.at(1) == 0) { + for (int i = 0; i < list.count(); ++i) + list[i] = d->qFileDialogUi->splitter->widget(i)->sizeHint().width(); + d->qFileDialogUi->splitter->setSizes(list); + } + + d->qFileDialogUi->sidebar->setUrls(bookmarks); + while (history.count() > 5) + history.pop_front(); + setHistory(history); + setDirectory(lastVisitedDir()->isEmpty() ? currentDirectory : *lastVisitedDir()); + if (!d->qFileDialogUi->treeView->header()->restoreState(headerData)) + return false; + + setViewMode(ViewMode(viewMode)); + return true; +} + +/*! + \reimp +*/ +void QFileDialog::changeEvent(QEvent *e) +{ + Q_D(QFileDialog); + if (e->type() == QEvent::LanguageChange) { + d->retranslateWindowTitle(); + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +QFileDialogPrivate::QFileDialogPrivate() + : +#ifndef QT_NO_PROXYMODEL + proxyModel(0), +#endif + model(0), + fileMode(QFileDialog::AnyFile), + acceptMode(QFileDialog::AcceptOpen), + currentHistoryLocation(-1), + renameAction(0), + deleteAction(0), + showHiddenAction(0), + useDefaultCaption(true), + defaultFileTypes(true), + fileNameLabelExplicitlySat(false), + nativeDialogInUse(false), +#ifdef Q_WS_MAC + mDelegate(0), +#ifndef QT_MAC_USE_COCOA + mDialog(0), + mDialogStarted(false), + mDialogClosed(true), +#endif +#endif + qFileDialogUi(0) +{ +} + +QFileDialogPrivate::~QFileDialogPrivate() +{ +} + +void QFileDialogPrivate::retranslateWindowTitle() +{ + Q_Q(QFileDialog); + if (!useDefaultCaption || setWindowTitle != q->windowTitle()) + return; + if (acceptMode == QFileDialog::AcceptOpen) { + if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) + q->setWindowTitle(QFileDialog::tr("Find Directory")); + else + q->setWindowTitle(QFileDialog::tr("Open")); + } else + q->setWindowTitle(QFileDialog::tr("Save As")); + + setWindowTitle = q->windowTitle(); +} + +void QFileDialogPrivate::setLastVisitedDirectory(const QString &dir) +{ + *lastVisitedDir() = dir; +} + +void QFileDialogPrivate::retranslateStrings() +{ + Q_Q(QFileDialog); + /* WIDGETS */ + if (defaultFileTypes) + q->setNameFilter(QFileDialog::tr("All Files (*)")); + + QList<QAction*> actions = qFileDialogUi->treeView->header()->actions(); + QAbstractItemModel *abstractModel = model; +#ifndef QT_NO_PROXYMODEL + if (proxyModel) + abstractModel = proxyModel; +#endif + int total = qMin(abstractModel->columnCount(QModelIndex()), actions.count() + 1); + for (int i = 1; i < total; ++i) { + actions.at(i - 1)->setText(QFileDialog::tr("Show ") + abstractModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()); + } + + /* MENU ACTIONS */ + renameAction->setText(QFileDialog::tr("&Rename")); + deleteAction->setText(QFileDialog::tr("&Delete")); + showHiddenAction->setText(QFileDialog::tr("Show &hidden files")); + newFolderAction->setText(QFileDialog::tr("&New Folder")); + qFileDialogUi->retranslateUi(q); + + if (!fileNameLabelExplicitlySat){ + if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) { + q->setLabelText(QFileDialog::FileName, QFileDialog::tr("Directory:")); + } else { + q->setLabelText(QFileDialog::FileName, QFileDialog::tr("File &name:")); + } + fileNameLabelExplicitlySat = false; + } +} + +void QFileDialogPrivate::emitFilesSelected(const QStringList &files) +{ + Q_Q(QFileDialog); + emit q->filesSelected(files); + if (files.count() == 1) + emit q->fileSelected(files.first()); +} + +bool QFileDialogPrivate::canBeNativeDialog() +{ + Q_Q(QFileDialog); + if (nativeDialogInUse) + return true; + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return false; + if (opts & QFileDialog::DontUseNativeDialog) + return false; + + QLatin1String staticName(QFileDialog::staticMetaObject.className()); + QLatin1String dynamicName(q->metaObject()->className()); + return (staticName == dynamicName); +} + +/*! + \since 4.5 + Sets the given \a option to be enabled if \a on is true; otherwise, + clears the given \a option. + + \sa options, testOption() +*/ +void QFileDialog::setOption(Option option, bool on) +{ + Q_D(QFileDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + \since 4.5 + + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QFileDialog::testOption(Option option) const +{ + Q_D(const QFileDialog); + return (d->opts & option) != 0; +} + +/*! + \property QFileDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QFileDialog::setOptions(Options options) +{ + Q_D(QFileDialog); + + Options changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + if (changed & DontResolveSymlinks) + d->model->setResolveSymlinks(!(options & DontResolveSymlinks)); + if (changed & ReadOnly) { + bool ro = (options & ReadOnly); + d->model->setReadOnly(ro); + d->qFileDialogUi->newFolderButton->setEnabled(!ro); + d->renameAction->setEnabled(!ro); + d->deleteAction->setEnabled(!ro); + } + if (changed & HideNameFilterDetails) + setNameFilters(d->nameFilters); + + if (changed & ShowDirsOnly) + setFilter((options & ShowDirsOnly) ? filter() & ~QDir::Files : filter() | QDir::Files); +} + +QFileDialog::Options QFileDialog::options() const +{ + Q_D(const QFileDialog); + return d->opts; +} + +/*! + \overload + + \since 4.5 + + This function connects one of its signals to the slot specified by \a receiver + and \a member. The specific signal depends is filesSelected() if fileMode is + ExistingFiles and fileSelected() if fileMode is anything else. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QFileDialog::open(QObject *receiver, const char *member) +{ + Q_D(QFileDialog); + const char *signal = (fileMode() == ExistingFiles) ? SIGNAL(filesSelected(QStringList)) + : SIGNAL(fileSelected(QString)); + connect(this, signal, receiver, member); + d->signalToDisconnectOnClose = signal; + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + + QDialog::open(); +} + + +/*! + \reimp +*/ +void QFileDialog::setVisible(bool visible) +{ + Q_D(QFileDialog); + if (visible){ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + + if (d->canBeNativeDialog()){ + if (d->setVisible_sys(visible)){ + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); +#ifndef QT_NO_FSCOMPLETER + //So the completer don't try to complete and therefore to show a popup + d->completer->setModel(0); +#endif + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); +#ifndef QT_NO_FSCOMPLETER + if (d->proxyModel != 0) + d->completer->setModel(d->proxyModel); + else + d->completer->setModel(d->model); +#endif + } + } + + if (!d->nativeDialogInUse) + d->qFileDialogUi->fileNameEdit->setFocus(); + + QDialog::setVisible(visible); +} + +/*! + \internal + set the directory to url +*/ +void QFileDialogPrivate::_q_goToUrl(const QUrl &url) +{ + //The shortcut in the side bar may have a parent that is not fetched yet (e.g. an hidden file) + //so we force the fetching + QFileSystemModelPrivate::QFileSystemNode *node = model->d_func()->node(url.toLocalFile(), true); + QModelIndex idx = model->d_func()->index(node); + _q_enterDirectory(idx); +} + +/*! + \fn void QFileDialog::setDirectory(const QDir &directory) + + \overload +*/ + +/*! + Sets the file dialog's current \a directory. +*/ +void QFileDialog::setDirectory(const QString &directory) +{ + Q_D(QFileDialog); + QString newDirectory = directory; + QFileInfo info(directory); + //we remove .. and . from the given path if exist + if (!directory.isEmpty()) + newDirectory = QDir::cleanPath(directory); + + if (!directory.isEmpty() && newDirectory.isEmpty()) + return; + + d->setLastVisitedDirectory(newDirectory); + + if (d->nativeDialogInUse){ + d->setDirectory_sys(newDirectory); + return; + } + if (d->rootPath() == newDirectory) + return; + QModelIndex root = d->model->setRootPath(newDirectory); + d->qFileDialogUi->newFolderButton->setEnabled(d->model->flags(root) & Qt::ItemIsDropEnabled); + if (root != d->rootIndex()) { +#ifndef QT_NO_FSCOMPLETER + if (directory.endsWith(QLatin1Char('/'))) + d->completer->setCompletionPrefix(newDirectory); + else + d->completer->setCompletionPrefix(newDirectory + QLatin1Char('/')); +#endif + d->setRootIndex(root); + } + d->qFileDialogUi->listView->selectionModel()->clear(); +} + +/*! + Returns the directory currently being displayed in the dialog. +*/ +QDir QFileDialog::directory() const +{ + Q_D(const QFileDialog); + return QDir(d->nativeDialogInUse ? d->directory_sys() : d->rootPath()); +} + +/*! + Selects the given \a filename in the file dialog. + + \sa selectedFiles() +*/ +void QFileDialog::selectFile(const QString &filename) +{ + Q_D(QFileDialog); + if (filename.isEmpty()) + return; + + if (d->nativeDialogInUse){ + d->selectFile_sys(filename); + return; + } + + if (!QDir::isRelativePath(filename)) { + QFileInfo info(filename); + QString filenamePath = info.absoluteDir().path(); + + if (d->model->rootPath() != filenamePath) + setDirectory(filenamePath); + } + + QModelIndex index = d->model->index(filename); + QString file; + if (!index.isValid()) { + // save as dialog where we want to input a default value + QString text = filename; + if (QFileInfo(filename).isAbsolute()) { + QString current = d->rootPath(); + text.remove(current); + if (text.at(0) == QDir::separator() +#ifdef Q_OS_WIN + //On Windows both cases can happen + || text.at(0) == QLatin1Char('/') +#endif + ) + text = text.remove(0,1); + } + file = text; + } else { + file = index.data().toString(); + } + d->qFileDialogUi->listView->selectionModel()->clear(); + if (!isVisible() || !d->lineEdit()->hasFocus()) + d->lineEdit()->setText(file); +} + +#ifdef Q_OS_UNIX +Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path, bool *expanded = 0) +{ + if (expanded != 0) + *expanded = false; + if (!path.startsWith(QLatin1Char('~'))) + return path; + QString ret = path; + QStringList tokens = ret.split(QDir::separator()); + if (tokens.first() == QLatin1String("~")) { + ret.replace(0, 1, QDir::homePath()); + } else { + QString userName = tokens.first(); + userName.remove(0, 1); +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) + passwd pw; + passwd *tmpPw; + char buf[200]; + const int bufSize = sizeof(buf); + int err = getpwnam_r(userName.toLocal8Bit().constData(), &pw, buf, bufSize, &tmpPw); + if (err || !tmpPw) + return ret; + const QString homePath = QString::fromLocal8Bit(pw.pw_dir); +#else + passwd *pw = getpwnam(userName.toLocal8Bit().constData()); + if (!pw) + return ret; + const QString homePath = QString::fromLocal8Bit(pw->pw_dir); +#endif + ret.replace(0, tokens.first().length(), homePath); + } + if (expanded != 0) + *expanded = true; + return ret; +} +#endif + +/** + Returns the text in the line edit which can be one or more file names + */ +QStringList QFileDialogPrivate::typedFiles() const +{ + Q_Q(const QFileDialog); + QStringList files; + QString editText = lineEdit()->text(); + if (!editText.contains(QLatin1Char('"'))) { +#ifdef Q_OS_UNIX + const QString prefix = q->directory().absolutePath() + QDir::separator(); + if (QFile::exists(prefix + editText)) + files << editText; + else + files << qt_tildeExpansion(editText); +#else + files << editText; +#endif + } else { + // " is used to separate files like so: "file1" "file2" "file3" ... + // ### need escape character for filenames with quotes (") + QStringList tokens = editText.split(QLatin1Char('\"')); + for (int i=0; i<tokens.size(); ++i) { + if ((i % 2) == 0) + continue; // Every even token is a separator +#ifdef Q_OS_UNIX + const QString token = tokens.at(i); + const QString prefix = q->directory().absolutePath() + QDir::separator(); + if (QFile::exists(prefix + token)) + files << token; + else + files << qt_tildeExpansion(token); +#else + files << toInternal(tokens.at(i)); +#endif + } + } + return addDefaultSuffixToFiles(files); +} + +QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList filesToFix) const +{ + QStringList files; + for (int i=0; i<filesToFix.size(); ++i) { + QString name = toInternal(filesToFix.at(i)); + QFileInfo info(name); + // if the filename has no suffix, add the default suffix + if (!defaultSuffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) + name += QLatin1Char('.') + defaultSuffix; + if (info.isAbsolute()) { + files.append(name); + } else { + // at this point the path should only have Qt path separators. + // This check is needed since we might be at the root directory + // and on Windows it already ends with slash. + QString path = rootPath(); + if (!path.endsWith(QLatin1Char('/'))) + path += QLatin1Char('/'); + path += name; + files.append(path); + } + } + return files; +} + + +/*! + Returns a list of strings containing the absolute paths of the + selected files in the dialog. If no files are selected, or + the mode is not ExistingFiles or ExistingFile, selectedFiles() contains the current path in the viewport. + + \sa selectedNameFilter(), selectFile() +*/ +QStringList QFileDialog::selectedFiles() const +{ + Q_D(const QFileDialog); + if (d->nativeDialogInUse) + return d->addDefaultSuffixToFiles(d->selectedFiles_sys()); + + QModelIndexList indexes = d->qFileDialogUi->listView->selectionModel()->selectedRows(); + QStringList files; + for (int i = 0; i < indexes.count(); ++i) + files.append(indexes.at(i).data(QFileSystemModel::FilePathRole).toString()); + + if (files.isEmpty() && !d->lineEdit()->text().isEmpty()) + files = d->typedFiles(); + + if (files.isEmpty() && !(d->fileMode == ExistingFile || d->fileMode == ExistingFiles)) + files.append(d->rootIndex().data(QFileSystemModel::FilePathRole).toString()); + return files; +} + +/* + Makes a list of filters from ;;-separated text. + Used by the mac and windows implementations +*/ +QStringList qt_make_filter_list(const QString &filter) +{ + QString f(filter); + + if (f.isEmpty()) + return QStringList(); + + QString sep(QLatin1String(";;")); + int i = f.indexOf(sep, 0); + if (i == -1) { + if (f.indexOf(QLatin1Char('\n'), 0) != -1) { + sep = QLatin1Char('\n'); + i = f.indexOf(sep, 0); + } + } + + return f.split(sep); +} + +/*! + \since 4.4 + + Sets the filter used in the file dialog to the given \a filter. + + If \a filter contains a pair of parentheses containing one or more + of \bold{anything*something}, separated by spaces, then only the + text contained in the parentheses is used as the filter. This means + that these calls are all equivalent: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 6 + + \sa setNameFilters() +*/ +void QFileDialog::setNameFilter(const QString &filter) +{ + setNameFilters(qt_make_filter_list(filter)); +} + +/*! + \obsolete + + Use setNameFilter() instead. +*/ +void QFileDialog::setFilter(const QString &filter) +{ + setNameFilter(filter); +} + +/*! + \property QFileDialog::nameFilterDetailsVisible + \obsolete + \brief This property holds whether the filter details is shown or not. + \since 4.4 + + When this property is true (the default), the filter details are shown + in the combo box. When the property is set to false, these are hidden. + + Use setOption(HideNameFilterDetails, !\e enabled) or + !testOption(HideNameFilterDetails). +*/ +void QFileDialog::setNameFilterDetailsVisible(bool enabled) +{ + setOption(HideNameFilterDetails, !enabled); +} + +bool QFileDialog::isNameFilterDetailsVisible() const +{ + return !testOption(HideNameFilterDetails); +} + + +/* + Strip the filters by removing the details, e.g. (*.*). +*/ +QStringList qt_strip_filters(const QStringList &filters) +{ + QStringList strippedFilters; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + for (int i = 0; i < filters.count(); ++i) { + QString filterName; + int index = r.indexIn(filters[i]); + if (index >= 0) + filterName = r.cap(1); + strippedFilters.append(filterName.simplified()); + } + return strippedFilters; +} + + +/*! + \since 4.4 + + Sets the \a filters used in the file dialog. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 7 +*/ +void QFileDialog::setNameFilters(const QStringList &filters) +{ + Q_D(QFileDialog); + d->defaultFileTypes = (filters == QStringList(QFileDialog::tr("All Files (*)"))); + QStringList cleanedFilters; + for (int i = 0; i < filters.count(); ++i) { + cleanedFilters << filters[i].simplified(); + } + d->nameFilters = cleanedFilters; + + if (d->nativeDialogInUse){ + d->setNameFilters_sys(cleanedFilters); + return; + } + + d->qFileDialogUi->fileTypeCombo->clear(); + if (cleanedFilters.isEmpty()) + return; + + if (testOption(HideNameFilterDetails)) + d->qFileDialogUi->fileTypeCombo->addItems(qt_strip_filters(cleanedFilters)); + else + d->qFileDialogUi->fileTypeCombo->addItems(cleanedFilters); + + d->_q_useNameFilter(0); +} + +/*! + \obsolete + + Use setNameFilters() instead. +*/ +void QFileDialog::setFilters(const QStringList &filters) +{ + setNameFilters(filters); +} + +/*! + \since 4.4 + + Returns the file type filters that are in operation on this file + dialog. +*/ +QStringList QFileDialog::nameFilters() const +{ + return d_func()->nameFilters; +} + +/*! + \obsolete + + Use nameFilters() instead. +*/ + +QStringList QFileDialog::filters() const +{ + return nameFilters(); +} + +/*! + \since 4.4 + + Sets the current file type \a filter. Multiple filters can be + passed in \a filter by separating them with semicolons or spaces. + + \sa setNameFilter(), setNameFilters(), selectedNameFilter() +*/ +void QFileDialog::selectNameFilter(const QString &filter) +{ + Q_D(QFileDialog); + if (d->nativeDialogInUse) { + d->selectNameFilter_sys(filter); + return; + } + int i; + if (testOption(HideNameFilterDetails)) { + i = d->qFileDialogUi->fileTypeCombo->findText(qt_strip_filters(qt_make_filter_list(filter)).first()); + } else { + i = d->qFileDialogUi->fileTypeCombo->findText(filter); + } + if (i >= 0) { + d->qFileDialogUi->fileTypeCombo->setCurrentIndex(i); + d->_q_useNameFilter(d->qFileDialogUi->fileTypeCombo->currentIndex()); + } +} + +/*! + \obsolete + + Use selectNameFilter() instead. +*/ + +void QFileDialog::selectFilter(const QString &filter) +{ + selectNameFilter(filter); +} + +/*! + \since 4.4 + + Returns the filter that the user selected in the file dialog. + + \sa selectedFiles() +*/ +QString QFileDialog::selectedNameFilter() const +{ + Q_D(const QFileDialog); + if (d->nativeDialogInUse) + return d->selectedNameFilter_sys(); + + return d->qFileDialogUi->fileTypeCombo->currentText(); +} + +/*! + \obsolete + + Use selectedNameFilter() instead. +*/ +QString QFileDialog::selectedFilter() const +{ + return selectedNameFilter(); +} + +/*! + \since 4.4 + + Returns the filter that is used when displaying files. + + \sa setFilter() +*/ +QDir::Filters QFileDialog::filter() const +{ + Q_D(const QFileDialog); + return d->model->filter(); +} + +/*! + \since 4.4 + + Sets the filter used by the model to \a filters. The filter is used + to specify the kind of files that should be shown. + + \sa filter() +*/ + +void QFileDialog::setFilter(QDir::Filters filters) +{ + Q_D(QFileDialog); + d->model->setFilter(filters); + if (d->nativeDialogInUse){ + d->setFilter_sys(); + return; + } + + d->showHiddenAction->setChecked((filters & QDir::Hidden)); +} + +/*! + \property QFileDialog::viewMode + \brief the way files and directories are displayed in the dialog + + By default, the \c Detail mode is used to display information about + files and directories. + + \sa ViewMode +*/ +void QFileDialog::setViewMode(QFileDialog::ViewMode mode) +{ + Q_D(QFileDialog); + if (mode == Detail) + d->_q_showDetailsView(); + else + d->_q_showListView(); +} + +QFileDialog::ViewMode QFileDialog::viewMode() const +{ + Q_D(const QFileDialog); + return (d->qFileDialogUi->stackedWidget->currentWidget() == d->qFileDialogUi->listView->parent() ? QFileDialog::List : QFileDialog::Detail); +} + +/*! + \property QFileDialog::fileMode + \brief the file mode of the dialog + + The file mode defines the number and type of items that the user is + expected to select in the dialog. + + By default, this property is set to AnyFile. + + This function will set the labels for the FileName and + \l{QFileDialog::}{Accept} \l{DialogLabel}s. It is possible to set + custom text after the call to setFileMode(). + + \sa FileMode +*/ +void QFileDialog::setFileMode(QFileDialog::FileMode mode) +{ + Q_D(QFileDialog); + d->fileMode = mode; + d->retranslateWindowTitle(); + + // keep ShowDirsOnly option in sync with fileMode (BTW, DirectoryOnly is obsolete) + setOption(ShowDirsOnly, mode == DirectoryOnly); + + // set selection mode and behavior + QAbstractItemView::SelectionMode selectionMode; + if (mode == QFileDialog::ExistingFiles) + selectionMode = QAbstractItemView::ExtendedSelection; + else + selectionMode = QAbstractItemView::SingleSelection; + d->qFileDialogUi->listView->setSelectionMode(selectionMode); + d->qFileDialogUi->treeView->setSelectionMode(selectionMode); + // set filter + d->model->setFilter(d->filterForMode(filter())); + // setup file type for directory + QString buttonText = (d->acceptMode == AcceptOpen ? tr("&Open") : tr("&Save")); + if (mode == DirectoryOnly || mode == Directory) { + d->qFileDialogUi->fileTypeCombo->clear(); + d->qFileDialogUi->fileTypeCombo->addItem(tr("Directories")); + d->qFileDialogUi->fileTypeCombo->setEnabled(false); + + if (!d->fileNameLabelExplicitlySat){ + setLabelText(FileName, tr("Directory:")); + d->fileNameLabelExplicitlySat = false; + } + buttonText = tr("&Choose"); + } else { + if (!d->fileNameLabelExplicitlySat){ + setLabelText(FileName, tr("File &name:")); + d->fileNameLabelExplicitlySat = false; + } + } + setLabelText(Accept, buttonText); + if (d->nativeDialogInUse){ + d->setFilter_sys(); + return; + } + + d->qFileDialogUi->fileTypeCombo->setEnabled(!testOption(ShowDirsOnly)); + d->_q_updateOkButton(); +} + +QFileDialog::FileMode QFileDialog::fileMode() const +{ + Q_D(const QFileDialog); + return d->fileMode; +} + +/*! + \property QFileDialog::acceptMode + \brief the accept mode of the dialog + + The action mode defines whether the dialog is for opening or saving files. + + By default, this property is set to \l{AcceptOpen}. + + \sa AcceptMode +*/ +void QFileDialog::setAcceptMode(QFileDialog::AcceptMode mode) +{ + Q_D(QFileDialog); + d->acceptMode = mode; + bool directoryMode = (d->fileMode == Directory || d->fileMode == DirectoryOnly); + QDialogButtonBox::StandardButton button = (mode == AcceptOpen ? QDialogButtonBox::Open : QDialogButtonBox::Save); + d->qFileDialogUi->buttonBox->setStandardButtons(button | QDialogButtonBox::Cancel); + d->qFileDialogUi->buttonBox->button(button)->setEnabled(false); + d->_q_updateOkButton(); + if (mode == AcceptOpen && directoryMode) + setLabelText(Accept, tr("&Choose")); + else + setLabelText(Accept, (mode == AcceptOpen ? tr("&Open") : tr("&Save"))); + if (mode == AcceptSave) { + d->qFileDialogUi->lookInCombo->setEditable(false); + } + d->retranslateWindowTitle(); +#if defined(Q_WS_MAC) + d->deleteNativeDialog_sys(); + setAttribute(Qt::WA_DontShowOnScreen, false); +#endif +} + +/* + Returns the file system model index that is the root index in the + views +*/ +QModelIndex QFileDialogPrivate::rootIndex() const { + return mapToSource(qFileDialogUi->listView->rootIndex()); +} + +QAbstractItemView *QFileDialogPrivate::currentView() const { + if (!qFileDialogUi->stackedWidget) + return 0; + if (qFileDialogUi->stackedWidget->currentWidget() == qFileDialogUi->listView->parent()) + return qFileDialogUi->listView; + return qFileDialogUi->treeView; +} + +QLineEdit *QFileDialogPrivate::lineEdit() const { + return (QLineEdit*)qFileDialogUi->fileNameEdit; +} + +/* + Sets the view root index to be the file system model index +*/ +void QFileDialogPrivate::setRootIndex(const QModelIndex &index) const { + Q_ASSERT(index.isValid() ? index.model() == model : true); + QModelIndex idx = mapFromSource(index); + qFileDialogUi->treeView->setRootIndex(idx); + qFileDialogUi->listView->setRootIndex(idx); +} +/* + Select a file system model index + returns the index that was selected (or not depending upon sortfilterproxymodel) +*/ +QModelIndex QFileDialogPrivate::select(const QModelIndex &index) const { + Q_ASSERT(index.isValid() ? index.model() == model : true); + + QModelIndex idx = mapFromSource(index); + if (idx.isValid() && !qFileDialogUi->listView->selectionModel()->isSelected(idx)) + qFileDialogUi->listView->selectionModel()->select(idx, + QItemSelectionModel::Select | QItemSelectionModel::Rows); + return idx; +} + +QFileDialog::AcceptMode QFileDialog::acceptMode() const +{ + Q_D(const QFileDialog); + return d->acceptMode; +} + +/*! + \property QFileDialog::readOnly + \obsolete + \brief Whether the filedialog is read-only + + If this property is set to false, the file dialog will allow renaming, + and deleting of files and directories and creating directories. + + Use setOption(ReadOnly, \e enabled) or testOption(ReadOnly) instead. +*/ +void QFileDialog::setReadOnly(bool enabled) +{ + setOption(ReadOnly, enabled); +} + +bool QFileDialog::isReadOnly() const +{ + return testOption(ReadOnly); +} + +/*! + \property QFileDialog::resolveSymlinks + \obsolete + \brief whether the filedialog should resolve shortcuts + + If this property is set to true, the file dialog will resolve + shortcuts or symbolic links. + + Use setOption(DontResolveSymlinks, !\a enabled) or + !testOption(DontResolveSymlinks). +*/ +void QFileDialog::setResolveSymlinks(bool enabled) +{ + setOption(DontResolveSymlinks, !enabled); +} + +bool QFileDialog::resolveSymlinks() const +{ + return !testOption(DontResolveSymlinks); +} + +/*! + \property QFileDialog::confirmOverwrite + \obsolete + \brief whether the filedialog should ask before accepting a selected file, + when the accept mode is AcceptSave + + Use setOption(DontConfirmOverwrite, !\e enabled) or + !testOption(DontConfirmOverwrite) instead. +*/ +void QFileDialog::setConfirmOverwrite(bool enabled) +{ + setOption(DontConfirmOverwrite, !enabled); +} + +bool QFileDialog::confirmOverwrite() const +{ + return !testOption(DontConfirmOverwrite); +} + +/*! + \property QFileDialog::defaultSuffix + \brief suffix added to the filename if no other suffix was specified + + This property specifies a string that will be added to the + filename if it has no suffix already. The suffix is typically + used to indicate the file type (e.g. "txt" indicates a text + file). +*/ +void QFileDialog::setDefaultSuffix(const QString &suffix) +{ + Q_D(QFileDialog); + d->defaultSuffix = suffix; +} + +QString QFileDialog::defaultSuffix() const +{ + Q_D(const QFileDialog); + return d->defaultSuffix; +} + +/*! + Sets the browsing history of the filedialog to contain the given + \a paths. +*/ +void QFileDialog::setHistory(const QStringList &paths) +{ + Q_D(QFileDialog); + d->qFileDialogUi->lookInCombo->setHistory(paths); +} + +void QFileDialogComboBox::setHistory(const QStringList &paths) +{ + m_history = paths; + // Only populate the first item, showPopup will populate the rest if needed + QList<QUrl> list; + QModelIndex idx = d_ptr->model->index(d_ptr->rootPath()); + //On windows the popup display the "C:\", convert to nativeSeparators + QUrl url = QUrl::fromLocalFile(QDir::toNativeSeparators(idx.data(QFileSystemModel::FilePathRole).toString())); + if (url.isValid()) + list.append(url); + urlModel->setUrls(list); +} + +/*! + Returns the browsing history of the filedialog as a list of paths. +*/ +QStringList QFileDialog::history() const +{ + Q_D(const QFileDialog); + QStringList currentHistory = d->qFileDialogUi->lookInCombo->history(); + //On windows the popup display the "C:\", convert to nativeSeparators + QString newHistory = QDir::toNativeSeparators(d->rootIndex().data(QFileSystemModel::FilePathRole).toString()); + if (!currentHistory.contains(newHistory)) + currentHistory << newHistory; + return currentHistory; +} + +/*! + Sets the item delegate used to render items in the views in the + file dialog to the given \a delegate. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + Note that the model used is QFileSystemModel. It has custom item data roles, which is + described by the \l{QFileSystemModel::}{Roles} enum. You can use a QFileIconProvider if + you only want custom icons. + + \sa itemDelegate(), setIconProvider(), QFileSystemModel +*/ +void QFileDialog::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QFileDialog); + d->qFileDialogUi->listView->setItemDelegate(delegate); + d->qFileDialogUi->treeView->setItemDelegate(delegate); +} + +/*! + Returns the item delegate used to render the items in the views in the filedialog. +*/ +QAbstractItemDelegate *QFileDialog::itemDelegate() const +{ + Q_D(const QFileDialog); + return d->qFileDialogUi->listView->itemDelegate(); +} + +/*! + Sets the icon provider used by the filedialog to the specified \a provider. +*/ +void QFileDialog::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QFileDialog); + d->model->setIconProvider(provider); + //It forces the refresh of all entries in the side bar, then we can get new icons + d->qFileDialogUi->sidebar->setUrls(d->qFileDialogUi->sidebar->urls()); +} + +/*! + Returns the icon provider used by the filedialog. +*/ +QFileIconProvider *QFileDialog::iconProvider() const +{ + Q_D(const QFileDialog); + return d->model->iconProvider(); +} + +/*! + Sets the \a text shown in the filedialog in the specified \a label. +*/ +void QFileDialog::setLabelText(DialogLabel label, const QString &text) +{ + Q_D(QFileDialog); + QPushButton *button; + switch (label) { + case LookIn: + d->qFileDialogUi->lookInLabel->setText(text); + break; + case FileName: + d->qFileDialogUi->fileNameLabel->setText(text); + d->fileNameLabelExplicitlySat = true; + break; + case FileType: + d->qFileDialogUi->fileTypeLabel->setText(text); + break; + case Accept: + d->acceptLabel = text; + if (acceptMode() == AcceptOpen) + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Open); + else + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Save); + if (button) + button->setText(text); + break; + case Reject: + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel); + if (button) + button->setText(text); + break; + } +} + +/*! + Returns the text shown in the filedialog in the specified \a label. +*/ +QString QFileDialog::labelText(DialogLabel label) const +{ + QPushButton *button; + Q_D(const QFileDialog); + switch (label) { + case LookIn: + return d->qFileDialogUi->lookInLabel->text(); + case FileName: + return d->qFileDialogUi->fileNameLabel->text(); + case FileType: + return d->qFileDialogUi->fileTypeLabel->text(); + case Accept: + if (acceptMode() == AcceptOpen) + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Open); + else + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Save); + if (button) + return button->text(); + case Reject: + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel); + if (button) + return button->text(); + } + return QString(); +} + +/* + For the native file dialogs +*/ + +#if defined(Q_WS_WIN) +extern QString qt_win_get_open_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QString qt_win_get_save_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QStringList qt_win_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QString qt_win_get_existing_directory(const QFileDialogArgs &args); +#endif + +/* + For Symbian file dialogs +*/ +#if defined(Q_WS_S60) +extern QString qtSymbianGetOpenFileName(const QString &caption, + const QString &dir, + const QString &filter); + +extern QStringList qtSymbianGetOpenFileNames(const QString &caption, + const QString &dir, + const QString &filter); + +extern QString qtSymbianGetSaveFileName(const QString &caption, + const QString &dir); + +extern QString qtSymbianGetExistingDirectory(const QString &caption, + const QString &dir); +#endif + +/*! + This is a convenience static function that returns an existing file + selected by the user. If the user presses Cancel, it returns a null string. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 8 + + The function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. Only files that match the + given \a filter are shown. The filter selected is set to \a selectedFilter. + The parameters \a dir, \a selectedFilter, and \a filter may be empty + strings. If you want multiple filters, separate them with ';;', for + example: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + The dialog's caption is set to \a caption. If \a caption is not specified + then a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks, the file dialog will treat + symlinks as regular directories. + + On Symbian^3 the parameter \a selectedFilter has no meaning and the + \a options parameter is only used to define if the native file dialog is + used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileNames(), getSaveFileName(), getExistingDirectory() +*/ +QString QFileDialog::getOpenFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_open_filename_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_open_filename_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetOpenFileName(caption, dir, filter); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = ExistingFile; + args.options = options; +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_open_file_name(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles().value(0); + } + return QString(); +} + +/*! + This is a convenience static function that will return one or more existing + files selected by the user. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 9 + + This function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. The filter is set to + \a filter so that only those files which match the filter are shown. The + filter selected is set to \a selectedFilter. The parameters \a dir, + \a selectedFilter and \a filter may be empty strings. If you need multiple + filters, separate them with ';;', for instance: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The dialog's caption is set to \a caption. If \a caption is not specified + then a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + \note If you want to iterate over the list of files, you should iterate + over a copy. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 10 + + On Symbian^3 the parameter \a selectedFilter has no meaning and the + \a options parameter is only used to define if the native file dialog is + used. On Symbian^3, this function can only return a single filename. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getSaveFileName(), getExistingDirectory() +*/ +QStringList QFileDialog::getOpenFileNames(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_open_filenames_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_open_filenames_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetOpenFileNames(caption, dir, filter); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = ExistingFiles; + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_open_file_names(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles(); + } + return QStringList(); +} + +/*! + This is a convenience static function that will return a file name selected + by the user. The file does not have to exist. + + It creates a modal file dialog with the given \a parent widget. If + \a parent is not 0, the dialog will be shown centered over the parent + widget. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 11 + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. Only files that match the + \a filter are shown. The filter selected is set to \a selectedFilter. The + parameters \a dir, \a selectedFilter, and \a filter may be empty strings. + Multiple filters are separated with ';;'. For instance: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + The default filter can be chosen by setting \a selectedFilter to the + desired value. + + The dialog's caption is set to \a caption. If \a caption is not specified, + a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. On Mac OS X, with its native file + dialog, the filter argument is ignored. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks the file dialog will treat symlinks + as regular directories. + + On Symbian^3 the parameters \a filter and \a selectedFilter have no + meaning. The \a options parameter is only used to define if the native file + dialog is used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getOpenFileNames(), getExistingDirectory() +*/ +QString QFileDialog::getSaveFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_save_filename_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_save_filename_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetSaveFileName(caption, dir); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = AnyFile; + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_save_file_name(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + dialog.setAcceptMode(AcceptSave); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles().value(0); + } + + return QString(); +} + +/*! + This is a convenience static function that will return an existing + directory selected by the user. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 12 + + This function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The dialog's working directory is set to \a dir, and the caption is set to + \a caption. Either of these may be an empty string in which case the + current directory and a default caption will be used respectively. + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. To ensure a native file dialog, \l{QFileDialog::}{ShowDirsOnly} must + be set. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. On Windows CE, if the device has + no native file dialog, a QFileDialog will be used. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks, the file dialog will treat + symlinks as regular directories. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Symbian^3 the \a options parameter is only used to define if the native + file dialog is used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getOpenFileNames(), getSaveFileName() +*/ +QString QFileDialog::getExistingDirectory(QWidget *parent, + const QString &caption, + const QString &dir, + Options options) +{ + if (qt_filedialog_existing_directory_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_existing_directory_hook(parent, caption, dir, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetExistingDirectory(caption, dir); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.mode = (options & ShowDirsOnly ? DirectoryOnly : Directory); + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog) && (options & ShowDirsOnly) +#if defined(Q_WS_WINCE) + && qt_priv_ptr_valid +#endif + ) { + return qt_win_get_existing_directory(args); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (dialog.exec() == QDialog::Accepted) { + return dialog.selectedFiles().value(0); + } + return QString(); +} + +inline static QString _qt_get_directory(const QString &path) +{ + QFileInfo info = QFileInfo(QDir::current(), path); + if (info.exists() && info.isDir()) + return QDir::cleanPath(info.absoluteFilePath()); + info.setFile(info.absolutePath()); + if (info.exists() && info.isDir()) + return info.absoluteFilePath(); + return QString(); +} +/* + Get the initial directory path + + \sa initialSelection() + */ +QString QFileDialogPrivate::workingDirectory(const QString &path) +{ + if (!path.isEmpty()) { + QString directory = _qt_get_directory(path); + if (!directory.isEmpty()) + return directory; + } + QString directory = _qt_get_directory(*lastVisitedDir()); + if (!directory.isEmpty()) + return directory; + return QDir::currentPath(); +} + +/* + Get the initial selection given a path. The initial directory + can contain both the initial directory and initial selection + /home/user/foo.txt + + \sa workingDirectory() + */ +QString QFileDialogPrivate::initialSelection(const QString &path) +{ + if (!path.isEmpty()) { + QFileInfo info(path); + if (!info.isDir()) + return info.fileName(); + } + return QString(); +} + +/*! + \reimp +*/ +void QFileDialog::done(int result) +{ + Q_D(QFileDialog); + + QDialog::done(result); + + if (d->receiverToDisconnectOnClose) { + disconnect(this, d->signalToDisconnectOnClose, + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); + d->signalToDisconnectOnClose.clear(); +} + +/*! + \reimp +*/ +void QFileDialog::accept() +{ + Q_D(QFileDialog); + QStringList files = selectedFiles(); + if (files.isEmpty()) + return; + if (d->nativeDialogInUse){ + d->emitFilesSelected(files); + QDialog::accept(); + return; + } + + QString lineEditText = d->lineEdit()->text(); + // "hidden feature" type .. and then enter, and it will move up a dir + // special case for ".." + if (lineEditText == QLatin1String("..")) { + d->_q_navigateToParent(); + bool block = d->qFileDialogUi->fileNameEdit->blockSignals(true); + d->lineEdit()->selectAll(); + d->qFileDialogUi->fileNameEdit->blockSignals(block); + return; + } + + switch (d->fileMode) { + case DirectoryOnly: + case Directory: { + QString fn = files.first(); + QFileInfo info(fn); + if (!info.exists()) + info = QFileInfo(d->getEnvironmentVariable(fn)); + if (!info.exists()) { +#ifndef QT_NO_MESSAGEBOX + QString message = tr("%1\nDirectory not found.\nPlease verify the " + "correct directory name was given."); + QMessageBox::warning(this, windowTitle(), message.arg(info.fileName())); +#endif // QT_NO_MESSAGEBOX + return; + } + if (info.isDir()) { + d->emitFilesSelected(files); + QDialog::accept(); + } + return; + } + + case AnyFile: { + QString fn = files.first(); + QFileInfo info(fn); + if (info.isDir()) { + setDirectory(info.absoluteFilePath()); + return; + } + + if (!info.exists()) { + int maxNameLength = d->maxNameLength(info.path()); + if (maxNameLength >= 0 && info.fileName().length() > maxNameLength) + return; + } + + // check if we have to ask for permission to overwrite the file + if (!info.exists() || !confirmOverwrite() || acceptMode() == AcceptOpen) { + d->emitFilesSelected(QStringList(fn)); + QDialog::accept(); +#ifndef QT_NO_MESSAGEBOX + } else { + if (QMessageBox::warning(this, windowTitle(), + tr("%1 already exists.\nDo you want to replace it?") + .arg(info.fileName()), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + == QMessageBox::Yes) { + d->emitFilesSelected(QStringList(fn)); + QDialog::accept(); + } +#endif + } + return; + } + + case ExistingFile: + case ExistingFiles: + for (int i = 0; i < files.count(); ++i) { + QFileInfo info(files.at(i)); + if (!info.exists()) + info = QFileInfo(d->getEnvironmentVariable(files.at(i))); + if (!info.exists()) { +#ifndef QT_NO_MESSAGEBOX + QString message = tr("%1\nFile not found.\nPlease verify the " + "correct file name was given."); + QMessageBox::warning(this, windowTitle(), message.arg(info.fileName())); +#endif // QT_NO_MESSAGEBOX + return; + } + if (info.isDir()) { + setDirectory(info.absoluteFilePath()); + d->lineEdit()->clear(); + return; + } + } + d->emitFilesSelected(files); + QDialog::accept(); + return; + } +} + +/*! + \internal + + Create widgets, layout and set default values +*/ +void QFileDialogPrivate::init(const QString &directory, const QString &nameFilter, + const QString &caption) +{ + Q_Q(QFileDialog); + if (!caption.isEmpty()) { + useDefaultCaption = false; + setWindowTitle = caption; + q->setWindowTitle(caption); + } + + createWidgets(); + createMenuActions(); + retranslateStrings(); + q->setFileMode(fileMode); + +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + if (!directory.isEmpty()) + setLastVisitedDirectory(workingDirectory(directory)); + q->restoreState(settings.value(QLatin1String("filedialog")).toByteArray()); +#endif + +#if defined(Q_EMBEDDED_SMALLSCREEN) + qFileDialogUi->lookInLabel->setVisible(false); + qFileDialogUi->fileNameLabel->setVisible(false); + qFileDialogUi->fileTypeLabel->setVisible(false); + qFileDialogUi->sidebar->hide(); +#endif + // Default case + if (!nameFilter.isEmpty()) + q->setNameFilter(nameFilter); + q->setAcceptMode(QFileDialog::AcceptOpen); + q->setDirectory(workingDirectory(directory)); + q->selectFile(initialSelection(directory)); + + _q_updateOkButton(); + q->resize(q->sizeHint()); +} + +/*! + \internal + + Create the widgets, set properties and connections +*/ +void QFileDialogPrivate::createWidgets() +{ + Q_Q(QFileDialog); + model = new QFileSystemModel(q); + model->setObjectName(QLatin1String("qt_filesystem_model")); +#ifdef Q_WS_MAC + model->setNameFilterDisables(true); +#else + model->setNameFilterDisables(false); +#endif + model->d_func()->disableRecursiveSort = true; + QFileDialog::connect(model, SIGNAL(fileRenamed(QString,QString,QString)), q, SLOT(_q_fileRenamed(QString,QString,QString))); + QFileDialog::connect(model, SIGNAL(rootPathChanged(QString)), + q, SLOT(_q_pathChanged(QString))); + QFileDialog::connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(_q_rowsInserted(QModelIndex))); + model->setReadOnly(false); + + qFileDialogUi.reset(new Ui_QFileDialog()); + qFileDialogUi->setupUi(q); + + QList<QUrl> initialBookmarks; + initialBookmarks << QUrl::fromLocalFile(QLatin1String("")) + << QUrl::fromLocalFile(QDir::homePath()); + qFileDialogUi->sidebar->init(model, initialBookmarks); + QFileDialog::connect(qFileDialogUi->sidebar, SIGNAL(goToUrl(QUrl)), + q, SLOT(_q_goToUrl(QUrl))); + + QObject::connect(qFileDialogUi->buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + QObject::connect(qFileDialogUi->buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + + + qFileDialogUi->lookInCombo->init(this); + QObject::connect(qFileDialogUi->lookInCombo, SIGNAL(activated(QString)), q, SLOT(_q_goToDirectory(QString))); + + qFileDialogUi->lookInCombo->setInsertPolicy(QComboBox::NoInsert); + qFileDialogUi->lookInCombo->setDuplicatesEnabled(false); + + // filename + qFileDialogUi->fileNameEdit->init(this); +#ifndef QT_NO_SHORTCUT + qFileDialogUi->fileNameLabel->setBuddy(qFileDialogUi->fileNameEdit); +#endif +#ifndef QT_NO_FSCOMPLETER + completer = new QFSCompleter(model, q); + qFileDialogUi->fileNameEdit->setCompleter(completer); +#endif // QT_NO_FSCOMPLETER + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_autoCompleteFileName(QString))); + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_updateOkButton())); + + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(returnPressed()), q, SLOT(accept())); + + // filetype + qFileDialogUi->fileTypeCombo->setDuplicatesEnabled(false); + qFileDialogUi->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + qFileDialogUi->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + QObject::connect(qFileDialogUi->fileTypeCombo, SIGNAL(activated(int)), + q, SLOT(_q_useNameFilter(int))); + QObject::connect(qFileDialogUi->fileTypeCombo, SIGNAL(activated(QString)), + q, SIGNAL(filterSelected(QString))); + + qFileDialogUi->listView->init(this); + qFileDialogUi->listView->setModel(model); + QObject::connect(qFileDialogUi->listView, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_enterDirectory(QModelIndex))); + QObject::connect(qFileDialogUi->listView, SIGNAL(customContextMenuRequested(QPoint)), + q, SLOT(_q_showContextMenu(QPoint))); +#ifndef QT_NO_SHORTCUT + QShortcut *shortcut = new QShortcut(qFileDialogUi->listView); + shortcut->setKey(QKeySequence(QLatin1String("Delete"))); + QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent())); +#endif + + qFileDialogUi->treeView->init(this); + qFileDialogUi->treeView->setModel(model); + QHeaderView *treeHeader = qFileDialogUi->treeView->header(); + QFontMetrics fm(q->font()); + treeHeader->resizeSection(0, fm.width(QLatin1String("wwwwwwwwwwwwwwwwwwwwwwwwww"))); + treeHeader->resizeSection(1, fm.width(QLatin1String("128.88 GB"))); + treeHeader->resizeSection(2, fm.width(QLatin1String("mp3Folder"))); + treeHeader->resizeSection(3, fm.width(QLatin1String("10/29/81 02:02PM"))); + treeHeader->setContextMenuPolicy(Qt::ActionsContextMenu); + + QActionGroup *showActionGroup = new QActionGroup(q); + showActionGroup->setExclusive(false); + QObject::connect(showActionGroup, SIGNAL(triggered(QAction*)), + q, SLOT(_q_showHeader(QAction*)));; + + QAbstractItemModel *abstractModel = model; +#ifndef QT_NO_PROXYMODEL + if (proxyModel) + abstractModel = proxyModel; +#endif + for (int i = 1; i < abstractModel->columnCount(QModelIndex()); ++i) { + QAction *showHeader = new QAction(showActionGroup); + showHeader->setCheckable(true); + showHeader->setChecked(true); + treeHeader->addAction(showHeader); + } + + QScopedPointer<QItemSelectionModel> selModel(qFileDialogUi->treeView->selectionModel()); + qFileDialogUi->treeView->setSelectionModel(qFileDialogUi->listView->selectionModel()); + + QObject::connect(qFileDialogUi->treeView, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_enterDirectory(QModelIndex))); + QObject::connect(qFileDialogUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), + q, SLOT(_q_showContextMenu(QPoint))); +#ifndef QT_NO_SHORTCUT + shortcut = new QShortcut(qFileDialogUi->treeView); + shortcut->setKey(QKeySequence(QLatin1String("Delete"))); + QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent())); +#endif + + // Selections + QItemSelectionModel *selections = qFileDialogUi->listView->selectionModel(); + QObject::connect(selections, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SLOT(_q_selectionChanged())); + QObject::connect(selections, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_currentChanged(QModelIndex))); + qFileDialogUi->splitter->setStretchFactor(qFileDialogUi->splitter->indexOf(qFileDialogUi->splitter->widget(1)), QSizePolicy::Expanding); + + createToolButtons(); +} + +void QFileDialogPrivate::_q_showHeader(QAction *action) +{ + Q_Q(QFileDialog); + QActionGroup *actionGroup = qobject_cast<QActionGroup*>(q->sender()); + qFileDialogUi->treeView->header()->setSectionHidden(actionGroup->actions().indexOf(action) + 1, !action->isChecked()); +} + +#ifndef QT_NO_PROXYMODEL +/*! + \since 4.3 + + Sets the model for the views to the given \a proxyModel. This is useful if you + want to modify the underlying model; for example, to add columns, filter + data or add drives. + + Any existing proxy model will be removed, but not deleted. The file dialog + will take ownership of the \a proxyModel. + + \sa proxyModel() +*/ +void QFileDialog::setProxyModel(QAbstractProxyModel *proxyModel) +{ + Q_D(QFileDialog); + if ((!proxyModel && !d->proxyModel) + || (proxyModel == d->proxyModel)) + return; + + QModelIndex idx = d->rootIndex(); + if (d->proxyModel) { + disconnect(d->proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } else { + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } + + if (proxyModel != 0) { + proxyModel->setParent(this); + d->proxyModel = proxyModel; + proxyModel->setSourceModel(d->model); + d->qFileDialogUi->listView->setModel(d->proxyModel); + d->qFileDialogUi->treeView->setModel(d->proxyModel); +#ifndef QT_NO_FSCOMPLETER + d->completer->setModel(d->proxyModel); + d->completer->proxyModel = d->proxyModel; +#endif + connect(d->proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } else { + d->proxyModel = 0; + d->qFileDialogUi->listView->setModel(d->model); + d->qFileDialogUi->treeView->setModel(d->model); +#ifndef QT_NO_FSCOMPLETER + d->completer->setModel(d->model); + d->completer->sourceModel = d->model; + d->completer->proxyModel = 0; +#endif + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } + QScopedPointer<QItemSelectionModel> selModel(d->qFileDialogUi->treeView->selectionModel()); + d->qFileDialogUi->treeView->setSelectionModel(d->qFileDialogUi->listView->selectionModel()); + + d->setRootIndex(idx); + + // reconnect selection + QItemSelectionModel *selections = d->qFileDialogUi->listView->selectionModel(); + QObject::connect(selections, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(_q_selectionChanged())); + QObject::connect(selections, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex))); +} + +/*! + Returns the proxy model used by the file dialog. By default no proxy is set. + + \sa setProxyModel() +*/ +QAbstractProxyModel *QFileDialog::proxyModel() const +{ + Q_D(const QFileDialog); + return d->proxyModel; +} +#endif // QT_NO_PROXYMODEL + +/*! + \internal + + Create tool buttons, set properties and connections +*/ +void QFileDialogPrivate::createToolButtons() +{ + Q_Q(QFileDialog); + qFileDialogUi->backButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowBack, 0, q)); + qFileDialogUi->backButton->setAutoRaise(true); + qFileDialogUi->backButton->setEnabled(false); + QObject::connect(qFileDialogUi->backButton, SIGNAL(clicked()), q, SLOT(_q_navigateBackward())); + + qFileDialogUi->forwardButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowForward, 0, q)); + qFileDialogUi->forwardButton->setAutoRaise(true); + qFileDialogUi->forwardButton->setEnabled(false); + QObject::connect(qFileDialogUi->forwardButton, SIGNAL(clicked()), q, SLOT(_q_navigateForward())); + + qFileDialogUi->toParentButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogToParent, 0, q)); + qFileDialogUi->toParentButton->setAutoRaise(true); + qFileDialogUi->toParentButton->setEnabled(false); + QObject::connect(qFileDialogUi->toParentButton, SIGNAL(clicked()), q, SLOT(_q_navigateToParent())); + + qFileDialogUi->listModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogListView, 0, q)); + qFileDialogUi->listModeButton->setAutoRaise(true); + qFileDialogUi->listModeButton->setDown(true); + QObject::connect(qFileDialogUi->listModeButton, SIGNAL(clicked()), q, SLOT(_q_showListView())); + + qFileDialogUi->detailModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogDetailedView, 0, q)); + qFileDialogUi->detailModeButton->setAutoRaise(true); + QObject::connect(qFileDialogUi->detailModeButton, SIGNAL(clicked()), q, SLOT(_q_showDetailsView())); + + QSize toolSize(qFileDialogUi->fileNameEdit->sizeHint().height(), qFileDialogUi->fileNameEdit->sizeHint().height()); + qFileDialogUi->backButton->setFixedSize(toolSize); + qFileDialogUi->listModeButton->setFixedSize(toolSize); + qFileDialogUi->detailModeButton->setFixedSize(toolSize); + qFileDialogUi->forwardButton->setFixedSize(toolSize); + qFileDialogUi->toParentButton->setFixedSize(toolSize); + + qFileDialogUi->newFolderButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogNewFolder, 0, q)); + qFileDialogUi->newFolderButton->setFixedSize(toolSize); + qFileDialogUi->newFolderButton->setAutoRaise(true); + qFileDialogUi->newFolderButton->setEnabled(false); + QObject::connect(qFileDialogUi->newFolderButton, SIGNAL(clicked()), q, SLOT(_q_createDirectory())); +} + +/*! + \internal + + Create actions which will be used in the right click. +*/ +void QFileDialogPrivate::createMenuActions() +{ + Q_Q(QFileDialog); + + QAction *goHomeAction = new QAction(q); +#ifndef QT_NO_SHORTCUT + goHomeAction->setShortcut(Qt::CTRL + Qt::Key_H + Qt::SHIFT); +#endif + QObject::connect(goHomeAction, SIGNAL(triggered()), q, SLOT(_q_goHome())); + q->addAction(goHomeAction); + + // ### TODO add Desktop & Computer actions + + QAction *goToParent = new QAction(q); + goToParent->setObjectName(QLatin1String("qt_goto_parent_action")); +#ifndef QT_NO_SHORTCUT + goToParent->setShortcut(Qt::CTRL + Qt::UpArrow); +#endif + QObject::connect(goToParent, SIGNAL(triggered()), q, SLOT(_q_navigateToParent())); + q->addAction(goToParent); + + renameAction = new QAction(q); + renameAction->setEnabled(false); + renameAction->setObjectName(QLatin1String("qt_rename_action")); + QObject::connect(renameAction, SIGNAL(triggered()), q, SLOT(_q_renameCurrent())); + + deleteAction = new QAction(q); + deleteAction->setEnabled(false); + deleteAction->setObjectName(QLatin1String("qt_delete_action")); + QObject::connect(deleteAction, SIGNAL(triggered()), q, SLOT(_q_deleteCurrent())); + + showHiddenAction = new QAction(q); + showHiddenAction->setObjectName(QLatin1String("qt_show_hidden_action")); + showHiddenAction->setCheckable(true); + QObject::connect(showHiddenAction, SIGNAL(triggered()), q, SLOT(_q_showHidden())); + + newFolderAction = new QAction(q); + newFolderAction->setObjectName(QLatin1String("qt_new_folder_action")); + QObject::connect(newFolderAction, SIGNAL(triggered()), q, SLOT(_q_createDirectory())); +} + +void QFileDialogPrivate::_q_goHome() +{ + Q_Q(QFileDialog); + q->setDirectory(QDir::homePath()); +} + +/*! + \internal + + Update history with new path, buttons, and combo +*/ +void QFileDialogPrivate::_q_pathChanged(const QString &newPath) +{ + Q_Q(QFileDialog); + QDir dir(model->rootDirectory()); + qFileDialogUi->toParentButton->setEnabled(dir.exists()); + qFileDialogUi->sidebar->selectUrl(QUrl::fromLocalFile(newPath)); + q->setHistory(qFileDialogUi->lookInCombo->history()); + + if (currentHistoryLocation < 0 || currentHistory.value(currentHistoryLocation) != QDir::toNativeSeparators(newPath)) { + while (currentHistoryLocation >= 0 && currentHistoryLocation + 1 < currentHistory.count()) { + currentHistory.removeLast(); + } + currentHistory.append(QDir::toNativeSeparators(newPath)); + ++currentHistoryLocation; + } + qFileDialogUi->forwardButton->setEnabled(currentHistory.size() - currentHistoryLocation > 1); + qFileDialogUi->backButton->setEnabled(currentHistoryLocation > 0); +} + +/*! + \internal + + Navigates to the last directory viewed in the dialog. +*/ +void QFileDialogPrivate::_q_navigateBackward() +{ + Q_Q(QFileDialog); + if (!currentHistory.isEmpty() && currentHistoryLocation > 0) { + --currentHistoryLocation; + QString previousHistory = currentHistory.at(currentHistoryLocation); + q->setDirectory(previousHistory); + } +} + +/*! + \internal + + Navigates to the last directory viewed in the dialog. +*/ +void QFileDialogPrivate::_q_navigateForward() +{ + Q_Q(QFileDialog); + if (!currentHistory.isEmpty() && currentHistoryLocation < currentHistory.size() - 1) { + ++currentHistoryLocation; + QString nextHistory = currentHistory.at(currentHistoryLocation); + q->setDirectory(nextHistory); + } +} + +/*! + \internal + + Navigates to the parent directory of the currently displayed directory + in the dialog. +*/ +void QFileDialogPrivate::_q_navigateToParent() +{ + Q_Q(QFileDialog); + QDir dir(model->rootDirectory()); + QString newDirectory; + if (dir.isRoot()) { + newDirectory = model->myComputer().toString(); + } else { + dir.cdUp(); + newDirectory = dir.absolutePath(); + } + q->setDirectory(newDirectory); + emit q->directoryEntered(newDirectory); +} + +/*! + \internal + + Creates a new directory, first asking the user for a suitable name. +*/ +void QFileDialogPrivate::_q_createDirectory() +{ + Q_Q(QFileDialog); + qFileDialogUi->listView->clearSelection(); + + QString newFolderString = QFileDialog::tr("New Folder"); + QString folderName = newFolderString; + QString prefix = q->directory().absolutePath() + QDir::separator(); + if (QFile::exists(prefix + folderName)) { + qlonglong suffix = 2; + while (QFile::exists(prefix + folderName)) { + folderName = newFolderString + QString::number(suffix++); + } + } + + QModelIndex parent = rootIndex(); + QModelIndex index = model->mkdir(parent, folderName); + if (!index.isValid()) + return; + + index = select(index); + if (index.isValid()) { + qFileDialogUi->treeView->setCurrentIndex(index); + currentView()->edit(index); + } +} + +void QFileDialogPrivate::_q_showListView() +{ + qFileDialogUi->listModeButton->setDown(true); + qFileDialogUi->detailModeButton->setDown(false); + qFileDialogUi->treeView->hide(); + qFileDialogUi->listView->show(); + qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->listView->parentWidget()); + qFileDialogUi->listView->doItemsLayout(); +} + +void QFileDialogPrivate::_q_showDetailsView() +{ + qFileDialogUi->listModeButton->setDown(false); + qFileDialogUi->detailModeButton->setDown(true); + qFileDialogUi->listView->hide(); + qFileDialogUi->treeView->show(); + qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->treeView->parentWidget()); + qFileDialogUi->treeView->doItemsLayout(); +} + +/*! + \internal + + Show the context menu for the file/dir under position +*/ +void QFileDialogPrivate::_q_showContextMenu(const QPoint &position) +{ +#ifdef QT_NO_MENU + Q_UNUSED(position); +#else + Q_Q(QFileDialog); + QAbstractItemView *view = 0; + if (q->viewMode() == QFileDialog::Detail) + view = qFileDialogUi->treeView; + else + view = qFileDialogUi->listView; + QModelIndex index = view->indexAt(position); + index = mapToSource(index.sibling(index.row(), 0)); + + QMenu menu(view); + if (index.isValid()) { + // file context menu + QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt()); + renameAction->setEnabled(p & QFile::WriteUser); + menu.addAction(renameAction); + deleteAction->setEnabled(p & QFile::WriteUser); + menu.addAction(deleteAction); + menu.addSeparator(); + } + menu.addAction(showHiddenAction); + if (qFileDialogUi->newFolderButton->isVisible()) { + newFolderAction->setEnabled(qFileDialogUi->newFolderButton->isEnabled()); + menu.addAction(newFolderAction); + } + menu.exec(view->viewport()->mapToGlobal(position)); +#endif // QT_NO_MENU +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_renameCurrent() +{ + Q_Q(QFileDialog); + QModelIndex index = qFileDialogUi->listView->currentIndex(); + index = index.sibling(index.row(), 0); + if (q->viewMode() == QFileDialog::List) + qFileDialogUi->listView->edit(index); + else + qFileDialogUi->treeView->edit(index); +} + +bool QFileDialogPrivate::removeDirectory(const QString &path) +{ + QModelIndex modelIndex = model->index(path); + return model->remove(modelIndex); +} + +/*! + \internal + + Deletes the currently selected item in the dialog. +*/ +void QFileDialogPrivate::_q_deleteCurrent() +{ + if (model->isReadOnly()) + return; + + QModelIndexList list = qFileDialogUi->listView->selectionModel()->selectedRows(); + for (int i = list.count() - 1; i >= 0; --i) { + QModelIndex index = list.at(i); + if (index == qFileDialogUi->listView->rootIndex()) + continue; + + index = mapToSource(index.sibling(index.row(), 0)); + if (!index.isValid()) + continue; + + QString fileName = index.data(QFileSystemModel::FileNameRole).toString(); + QString filePath = index.data(QFileSystemModel::FilePathRole).toString(); + bool isDir = model->isDir(index); + + QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt()); +#ifndef QT_NO_MESSAGEBOX + Q_Q(QFileDialog); + if (!(p & QFile::WriteUser) && (QMessageBox::warning(q_func(), q_func()->windowTitle(), + QFileDialog::tr("'%1' is write protected.\nDo you want to delete it anyway?") + .arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No)) + return; + else if (QMessageBox::warning(q_func(), q_func()->windowTitle(), + QFileDialog::tr("Are sure you want to delete '%1'?") + .arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) + return; + +#else + if (!(p & QFile::WriteUser)) + return; +#endif // QT_NO_MESSAGEBOX + + // the event loop has run, we can NOT reuse index because the model might have removed it. + if (isDir) { + if (!removeDirectory(filePath)) { +#ifndef QT_NO_MESSAGEBOX + QMessageBox::warning(q, q->windowTitle(), + QFileDialog::tr("Could not delete directory.")); +#endif + } + } else { + model->remove(index); + } + } +} + +void QFileDialogPrivate::_q_autoCompleteFileName(const QString &text) +{ + if (text.startsWith(QLatin1String("//")) || text.startsWith(QLatin1Char('\\'))) { + qFileDialogUi->listView->selectionModel()->clearSelection(); + return; + } + + QStringList multipleFiles = typedFiles(); + if (multipleFiles.count() > 0) { + QModelIndexList oldFiles = qFileDialogUi->listView->selectionModel()->selectedRows(); + QModelIndexList newFiles; + for (int i = 0; i < multipleFiles.count(); ++i) { + QModelIndex idx = model->index(multipleFiles.at(i)); + if (oldFiles.contains(idx)) + oldFiles.removeAll(idx); + else + newFiles.append(idx); + } + for (int i = 0; i < newFiles.count(); ++i) + select(newFiles.at(i)); + if (lineEdit()->hasFocus()) + for (int i = 0; i < oldFiles.count(); ++i) + qFileDialogUi->listView->selectionModel()->select(oldFiles.at(i), + QItemSelectionModel::Toggle | QItemSelectionModel::Rows); + } +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_updateOkButton() +{ + Q_Q(QFileDialog); + QPushButton *button = qFileDialogUi->buttonBox->button((acceptMode == QFileDialog::AcceptOpen) + ? QDialogButtonBox::Open : QDialogButtonBox::Save); + if (!button) + return; + + bool enableButton = true; + bool isOpenDirectory = false; + + QStringList files = q->selectedFiles(); + QString lineEditText = lineEdit()->text(); + + if (lineEditText.startsWith(QLatin1String("//")) || lineEditText.startsWith(QLatin1Char('\\'))) { + button->setEnabled(true); + if (acceptMode == QFileDialog::AcceptSave) + button->setText(acceptLabel); + return; + } + + if (files.isEmpty()) { + enableButton = false; + } else if (lineEditText == QLatin1String("..")) { + isOpenDirectory = true; + } else { + switch (fileMode) { + case QFileDialog::DirectoryOnly: + case QFileDialog::Directory: { + QString fn = files.first(); + QModelIndex idx = model->index(fn); + if (!idx.isValid()) + idx = model->index(getEnvironmentVariable(fn)); + if (!idx.isValid() || !model->isDir(idx)) + enableButton = false; + break; + } + case QFileDialog::AnyFile: { + QString fn = files.first(); + QFileInfo info(fn); + QModelIndex idx = model->index(fn); + QString fileDir; + QString fileName; + if (info.isDir()) { + fileDir = info.canonicalFilePath(); + } else { + fileDir = fn.mid(0, fn.lastIndexOf(QLatin1Char('/'))); + fileName = fn.mid(fileDir.length() + 1); + } + if (lineEditText.contains(QLatin1String(".."))) { + fileDir = info.canonicalFilePath(); + fileName = info.fileName(); + } + + if (fileDir == q->directory().canonicalPath() && fileName.isEmpty()) { + enableButton = false; + break; + } + if (idx.isValid() && model->isDir(idx)) { + isOpenDirectory = true; + enableButton = true; + break; + } + if (!idx.isValid()) { + int maxLength = maxNameLength(fileDir); + enableButton = maxLength < 0 || fileName.length() <= maxLength; + } + break; + } + case QFileDialog::ExistingFile: + case QFileDialog::ExistingFiles: + for (int i = 0; i < files.count(); ++i) { + QModelIndex idx = model->index(files.at(i)); + if (!idx.isValid()) + idx = model->index(getEnvironmentVariable(files.at(i))); + if (!idx.isValid()) { + enableButton = false; + break; + } + if (idx.isValid() && model->isDir(idx)) { + isOpenDirectory = true; + break; + } + } + break; + default: + break; + } + } + + button->setEnabled(enableButton); + if (acceptMode == QFileDialog::AcceptSave) + button->setText(isOpenDirectory ? QFileDialog::tr("&Open") : acceptLabel); +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_currentChanged(const QModelIndex &index) +{ + _q_updateOkButton(); + emit q_func()->currentChanged(index.data(QFileSystemModel::FilePathRole).toString()); +} + +/*! + \internal + + This is called when the user double clicks on a file with the corresponding + model item \a index. +*/ +void QFileDialogPrivate::_q_enterDirectory(const QModelIndex &index) +{ + Q_Q(QFileDialog); + // My Computer or a directory + QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index; + QString path = sourceIndex.data(QFileSystemModel::FilePathRole).toString(); + if (path.isEmpty() || model->isDir(sourceIndex)) { + q->setDirectory(path); + emit q->directoryEntered(path); + if (fileMode == QFileDialog::Directory + || fileMode == QFileDialog::DirectoryOnly) { + // ### find out why you have to do both of these. + lineEdit()->setText(QString()); + lineEdit()->clear(); + } + } else { + q->accept(); + } +} + +/*! + \internal + + Changes the file dialog's current directory to the one specified + by \a path. +*/ +void QFileDialogPrivate::_q_goToDirectory(const QString &path) +{ + #ifndef QT_NO_MESSAGEBOX + Q_Q(QFileDialog); +#endif + QModelIndex index = qFileDialogUi->lookInCombo->model()->index(qFileDialogUi->lookInCombo->currentIndex(), + qFileDialogUi->lookInCombo->modelColumn(), + qFileDialogUi->lookInCombo->rootModelIndex()); + QString path2 = path; + if (!index.isValid()) + index = mapFromSource(model->index(getEnvironmentVariable(path))); + else { + path2 = index.data(UrlRole).toUrl().toLocalFile(); + index = mapFromSource(model->index(path2)); + } + QDir dir(path2); + if (!dir.exists()) + dir = getEnvironmentVariable(path2); + + if (dir.exists() || path2.isEmpty() || path2 == model->myComputer().toString()) { + _q_enterDirectory(index); +#ifndef QT_NO_MESSAGEBOX + } else { + QString message = QFileDialog::tr("%1\nDirectory not found.\nPlease verify the " + "correct directory name was given."); + QMessageBox::warning(q, q->windowTitle(), message.arg(path2)); +#endif // QT_NO_MESSAGEBOX + } +} + +// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)" +QStringList qt_clean_filter_list(const QString &filter) +{ + QRegExp regexp(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + QString f = filter; + int i = regexp.indexIn(f); + if (i >= 0) + f = regexp.cap(2); + return f.split(QLatin1Char(' '), QString::SkipEmptyParts); +} + +/*! + \internal + + Sets the current name filter to be nameFilter and + update the qFileDialogUi->fileNameEdit when in AcceptSave mode with the new extension. +*/ +void QFileDialogPrivate::_q_useNameFilter(int index) +{ + if (index == nameFilters.size()) { + QAbstractItemModel *comboModel = qFileDialogUi->fileTypeCombo->model(); + nameFilters.append(comboModel->index(comboModel->rowCount() - 1, 0).data().toString()); + } + + QString nameFilter = nameFilters.at(index); + QStringList newNameFilters = qt_clean_filter_list(nameFilter); + if (acceptMode == QFileDialog::AcceptSave) { + QString newNameFilterExtension; + if (newNameFilters.count() > 0) + newNameFilterExtension = QFileInfo(newNameFilters.at(0)).suffix(); + + QString fileName = lineEdit()->text(); + const QString fileNameExtension = QFileInfo(fileName).suffix(); + if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) { + const int fileNameExtensionLength = fileNameExtension.count(); + fileName.replace(fileName.count() - fileNameExtensionLength, + fileNameExtensionLength, newNameFilterExtension); + qFileDialogUi->listView->clearSelection(); + lineEdit()->setText(fileName); + } + } + + model->setNameFilters(newNameFilters); +} + +/*! + \internal + + This is called when the model index corresponding to the current file is changed + from \a index to \a current. +*/ +void QFileDialogPrivate::_q_selectionChanged() +{ + QModelIndexList indexes = qFileDialogUi->listView->selectionModel()->selectedRows(); + bool stripDirs = (fileMode != QFileDialog::DirectoryOnly && fileMode != QFileDialog::Directory); + + QStringList allFiles; + for (int i = 0; i < indexes.count(); ++i) { + if (stripDirs && model->isDir(mapToSource(indexes.at(i)))) + continue; + allFiles.append(indexes.at(i).data().toString()); + } + if (allFiles.count() > 1) + for (int i = 0; i < allFiles.count(); ++i) { + allFiles.replace(i, QString(QLatin1Char('"') + allFiles.at(i) + QLatin1Char('"'))); + } + + QString finalFiles = allFiles.join(QLatin1String(" ")); + if (!finalFiles.isEmpty() && !lineEdit()->hasFocus() && lineEdit()->isVisible()) + lineEdit()->setText(finalFiles); + else + _q_updateOkButton(); +} + +/*! + \internal + + Includes hidden files and directories in the items displayed in the dialog. +*/ +void QFileDialogPrivate::_q_showHidden() +{ + Q_Q(QFileDialog); + QDir::Filters dirFilters = q->filter(); + if (showHiddenAction->isChecked()) + dirFilters |= QDir::Hidden; + else + dirFilters &= ~QDir::Hidden; + q->setFilter(dirFilters); +} + +/*! + \internal + + When parent is root and rows have been inserted when none was there before + then select the first one. +*/ +void QFileDialogPrivate::_q_rowsInserted(const QModelIndex &parent) +{ + if (!qFileDialogUi->treeView + || parent != qFileDialogUi->treeView->rootIndex() + || !qFileDialogUi->treeView->selectionModel() + || qFileDialogUi->treeView->selectionModel()->hasSelection() + || qFileDialogUi->treeView->model()->rowCount(parent) == 0) + return; +} + +void QFileDialogPrivate::_q_fileRenamed(const QString &path, const QString oldName, const QString newName) +{ + if (fileMode == QFileDialog::Directory || fileMode == QFileDialog::DirectoryOnly) { + if (path == rootPath() && lineEdit()->text() == oldName) + lineEdit()->setText(newName); + } +} + +/*! + \internal + + For the list and tree view watch keys to goto parent and back in the history + + returns true if handled +*/ +bool QFileDialogPrivate::itemViewKeyboardEvent(QKeyEvent *event) { + + Q_Q(QFileDialog); + switch (event->key()) { + case Qt::Key_Backspace: + _q_navigateToParent(); + return true; + case Qt::Key_Back: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + return false; +#endif + case Qt::Key_Left: + if (event->key() == Qt::Key_Back || event->modifiers() == Qt::AltModifier) { + _q_navigateBackward(); + return true; + } + break; + case Qt::Key_Escape: + q->hide(); + return true; + default: + break; + } + return false; +} + +QString QFileDialogPrivate::getEnvironmentVariable(const QString &string) +{ +#ifdef Q_OS_UNIX + if (string.size() > 1 && string.startsWith(QLatin1Char('$'))) { + return QString::fromLocal8Bit(getenv(string.mid(1).toLatin1().constData())); + } +#else + if (string.size() > 2 && string.startsWith(QLatin1Char('%')) && string.endsWith(QLatin1Char('%'))) { + return QString::fromLocal8Bit(qgetenv(string.mid(1, string.size() - 2).toLatin1().constData())); + } +#endif + return string; +} + +void QFileDialogComboBox::init(QFileDialogPrivate *d_pointer) { + d_ptr = d_pointer; + urlModel = new QUrlModel(this); + urlModel->showFullPath = true; + urlModel->setFileSystemModel(d_ptr->model); + setModel(urlModel); +} + +void QFileDialogComboBox::showPopup() +{ + if (model()->rowCount() > 1) + QComboBox::showPopup(); + + urlModel->setUrls(QList<QUrl>()); + QList<QUrl> list; + QModelIndex idx = d_ptr->model->index(d_ptr->rootPath()); + while (idx.isValid()) { + QUrl url = QUrl::fromLocalFile(idx.data(QFileSystemModel::FilePathRole).toString()); + if (url.isValid()) + list.append(url); + idx = idx.parent(); + } + // add "my computer" + list.append(QUrl::fromLocalFile(QLatin1String(""))); + urlModel->addUrls(list, 0); + idx = model()->index(model()->rowCount() - 1, 0); + + // append history + QList<QUrl> urls; + for (int i = 0; i < m_history.count(); ++i) { + QUrl path = QUrl::fromLocalFile(m_history.at(i)); + if (!urls.contains(path)) + urls.prepend(path); + } + if (urls.count() > 0) { + model()->insertRow(model()->rowCount()); + idx = model()->index(model()->rowCount()-1, 0); + // ### TODO maybe add a horizontal line before this + model()->setData(idx, QFileDialog::tr("Recent Places")); + QStandardItemModel *m = qobject_cast<QStandardItemModel*>(model()); + if (m) { + Qt::ItemFlags flags = m->flags(idx); + flags &= ~Qt::ItemIsEnabled; + m->item(idx.row(), idx.column())->setFlags(flags); + } + urlModel->addUrls(urls, -1, false); + } + setCurrentIndex(0); + + QComboBox::showPopup(); +} + +// Exact same as QComboBox::paintEvent(), except we elide the text. +void QFileDialogComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + + QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, this); + int size = editRect.width() - opt.iconSize.width() - 4; + opt.currentText = opt.fontMetrics.elidedText(opt.currentText, Qt::ElideMiddle, size); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +QFileDialogListView::QFileDialogListView(QWidget *parent) : QListView(parent) +{ +} + +void QFileDialogListView::init(QFileDialogPrivate *d_pointer) +{ + d_ptr = d_pointer; + setSelectionBehavior(QAbstractItemView::SelectRows); + setWrapping(true); + setResizeMode(QListView::Adjust); + setEditTriggers(QAbstractItemView::EditKeyPressed); + setContextMenuPolicy(Qt::CustomContextMenu); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::InternalMove); +#endif +} + +QSize QFileDialogListView::sizeHint() const +{ + int height = qMax(10, sizeHintForRow(0)); + return QSize(QListView::sizeHint().width() * 2, height * 30); +} + +void QFileDialogListView::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QListView::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + if (!d_ptr->itemViewKeyboardEvent(e)) + QListView::keyPressEvent(e); + e->accept(); +} + +QFileDialogTreeView::QFileDialogTreeView(QWidget *parent) : QTreeView(parent) +{ +} + +void QFileDialogTreeView::init(QFileDialogPrivate *d_pointer) +{ + d_ptr = d_pointer; + setSelectionBehavior(QAbstractItemView::SelectRows); + setRootIsDecorated(false); + setItemsExpandable(false); + setSortingEnabled(true); + header()->setSortIndicator(0, Qt::AscendingOrder); + header()->setStretchLastSection(false); + setTextElideMode(Qt::ElideMiddle); + setEditTriggers(QAbstractItemView::EditKeyPressed); + setContextMenuPolicy(Qt::CustomContextMenu); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::InternalMove); +#endif +} + +void QFileDialogTreeView::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QTreeView::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + if (!d_ptr->itemViewKeyboardEvent(e)) + QTreeView::keyPressEvent(e); + e->accept(); +} + +QSize QFileDialogTreeView::sizeHint() const +{ + int height = qMax(10, sizeHintForRow(0)); + QSize sizeHint = header()->sizeHint(); + return QSize(sizeHint.width() * 4, height * 30); +} + +/*! + // FIXME: this is a hack to avoid propagating key press events + // to the dialog and from there to the "Ok" button +*/ +void QFileDialogLineEdit::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QLineEdit::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + int key = e->key(); + QLineEdit::keyPressEvent(e); + if (key != Qt::Key_Escape) + e->accept(); + if (hideOnEsc && (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter)) { + e->accept(); + hide(); + d_ptr->currentView()->setFocus(Qt::ShortcutFocusReason); + } +} + +#ifndef QT_NO_FSCOMPLETER + +QString QFSCompleter::pathFromIndex(const QModelIndex &index) const +{ + const QFileSystemModel *dirModel; + if (proxyModel) + dirModel = qobject_cast<const QFileSystemModel *>(proxyModel->sourceModel()); + else + dirModel = sourceModel; + QString currentLocation = dirModel->rootPath(); + QString path = index.data(QFileSystemModel::FilePathRole).toString(); + if (!currentLocation.isEmpty() && path.startsWith(currentLocation)) { +#if defined(Q_OS_UNIX) || defined(Q_OS_WINCE) + if (currentLocation == QDir::separator()) + return path.mid(currentLocation.length()); +#endif + if (currentLocation.endsWith(QLatin1Char('/'))) + return path.mid(currentLocation.length()); + else + return path.mid(currentLocation.length()+1); + } + return index.data(QFileSystemModel::FilePathRole).toString(); +} + +QStringList QFSCompleter::splitPath(const QString &path) const +{ + if (path.isEmpty()) + return QStringList(completionPrefix()); + + QString pathCopy = QDir::toNativeSeparators(path); + QString sep = QDir::separator(); +#if defined(Q_OS_SYMBIAN) + if (pathCopy == QLatin1String("\\")) + return QStringList(pathCopy); +#elif defined(Q_OS_WIN) + if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\")) + return QStringList(pathCopy); + QString doubleSlash(QLatin1String("\\\\")); + if (pathCopy.startsWith(doubleSlash)) + pathCopy = pathCopy.mid(2); + else + doubleSlash.clear(); +#elif defined(Q_OS_UNIX) + bool expanded; + pathCopy = qt_tildeExpansion(pathCopy, &expanded); + if (expanded) { + QFileSystemModel *dirModel; + if (proxyModel) + dirModel = qobject_cast<QFileSystemModel *>(proxyModel->sourceModel()); + else + dirModel = sourceModel; + dirModel->fetchMore(dirModel->index(pathCopy)); + } +#endif + + QRegExp re(QLatin1Char('[') + QRegExp::escape(sep) + QLatin1Char(']')); + +#if defined(Q_OS_SYMBIAN) + QStringList parts = pathCopy.split(re, QString::SkipEmptyParts); + if (pathCopy.endsWith(sep)) + parts.append(QString()); +#elif defined(Q_OS_WIN) + QStringList parts = pathCopy.split(re, QString::SkipEmptyParts); + if (!doubleSlash.isEmpty() && !parts.isEmpty()) + parts[0].prepend(doubleSlash); + if (pathCopy.endsWith(sep)) + parts.append(QString()); +#else + QStringList parts = pathCopy.split(re); + if (pathCopy[0] == sep[0]) // read the "/" at the beginning as the split removed it + parts[0] = sep[0]; +#endif + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + bool startsFromRoot = !parts.isEmpty() && parts[0].endsWith(QLatin1Char(':')); +#else + bool startsFromRoot = pathCopy[0] == sep[0]; +#endif + if (parts.count() == 1 || (parts.count() > 1 && !startsFromRoot)) { + const QFileSystemModel *dirModel; + if (proxyModel) + dirModel = qobject_cast<const QFileSystemModel *>(proxyModel->sourceModel()); + else + dirModel = sourceModel; + QString currentLocation = QDir::toNativeSeparators(dirModel->rootPath()); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (currentLocation.endsWith(QLatin1Char(':'))) + currentLocation.append(sep); +#endif + if (currentLocation.contains(sep) && path != currentLocation) { + QStringList currentLocationList = splitPath(currentLocation); + while (!currentLocationList.isEmpty() + && parts.count() > 0 + && parts.at(0) == QLatin1String("..")) { + parts.removeFirst(); + currentLocationList.removeLast(); + } + if (!currentLocationList.isEmpty() && currentLocationList.last().isEmpty()) + currentLocationList.removeLast(); + return currentLocationList + parts; + } + } + return parts; +} + +#endif // QT_NO_COMPLETER + +#ifdef QT3_SUPPORT +/*! + Use selectedFiles() instead. + + \oldcode + QString selected = dialog->selectedFile(); + \newcode + QStringList files = dialog->selectedFiles(); + QString selected; + if (!files.isEmpty()) + selected = files[0]; + \endcode +*/ +QString QFileDialog::selectedFile() const +{ + QStringList files = selectedFiles(); + return files.size() ? files.at(0) : QString(); +} + +/*! + \typedef QFileDialog::Mode + + Use QFileDialog::FileMode instead. +*/ + +/*! + \fn void QFileDialog::setMode(FileMode m) + + Use setFileMode() instead. +*/ + +/*! + \fn FileMode QFileDialog::mode() const + + Use fileMode() instead. +*/ + +/*! + \fn void QFileDialog::setDir(const QString &directory) + + Use setDirectory() instead. +*/ + +/*! + \fn void QFileDialog::setDir( const QDir &directory ) + + Use setDirectory() instead. +*/ + +/*! + \fn QStringList QFileDialog::getOpenFileNames(const QString &filter, + const QString &dir, QWidget *parent, const char* name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getOpenFileNames() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getOpenFileName(const QString &dir, + const QString &filter, QWidget *parent = 0, const char *name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getOpenFileName() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getSaveFileName(const QString &dir, + const QString &filter, QWidget *parent, const char *name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getSaveFileName() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getExistingDirectory(const QString &dir, + QWidget *parent, const char *name, const QString &caption, + bool dirOnly, bool resolveSymlinks) + + Use the getExistingDirectory() overload that takes \a parent as + the first argument instead. +*/ + +#endif // QT3_SUPPORT + +QT_END_NAMESPACE + +#include "moc_qfiledialog.cpp" + +#endif // QT_NO_FILEDIALOG diff --git a/src/widgets/dialogs/qfiledialog.h b/src/widgets/dialogs/qfiledialog.h new file mode 100644 index 0000000000..1fd7c25255 --- /dev/null +++ b/src/widgets/dialogs/qfiledialog.h @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEDIALOG_H +#define QFILEDIALOG_H + +#include <QtCore/qdir.h> +#include <QtCore/qstring.h> +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILEDIALOG + +class QModelIndex; +class QItemSelection; +struct QFileDialogArgs; +class QFileIconProvider; +class QFileDialogPrivate; +class QAbstractItemDelegate; +class QAbstractProxyModel; +class QUrl; + +class Q_WIDGETS_EXPORT QFileDialog : public QDialog +{ + Q_OBJECT + Q_ENUMS(ViewMode FileMode AcceptMode Option) + Q_FLAGS(Options) + Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode) + Q_PROPERTY(FileMode fileMode READ fileMode WRITE setFileMode) + Q_PROPERTY(AcceptMode acceptMode READ acceptMode WRITE setAcceptMode) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE false) + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks DESIGNABLE false) + Q_PROPERTY(bool confirmOverwrite READ confirmOverwrite WRITE setConfirmOverwrite DESIGNABLE false) + Q_PROPERTY(QString defaultSuffix READ defaultSuffix WRITE setDefaultSuffix) + Q_PROPERTY(bool nameFilterDetailsVisible READ isNameFilterDetailsVisible + WRITE setNameFilterDetailsVisible DESIGNABLE false) + Q_PROPERTY(Options options READ options WRITE setOptions) + +public: + enum ViewMode { Detail, List }; + enum FileMode { AnyFile, ExistingFile, Directory, ExistingFiles, DirectoryOnly }; + enum AcceptMode { AcceptOpen, AcceptSave }; + enum DialogLabel { LookIn, FileName, FileType, Accept, Reject }; + + // ### Rename to FileDialogOption and FileDialogOptions for Qt 5.0 + enum Option + { + ShowDirsOnly = 0x00000001, + DontResolveSymlinks = 0x00000002, + DontConfirmOverwrite = 0x00000004, + DontUseSheet = 0x00000008, + DontUseNativeDialog = 0x00000010, + ReadOnly = 0x00000020, + HideNameFilterDetails = 0x00000040 + }; + Q_DECLARE_FLAGS(Options, Option) + + QFileDialog(QWidget *parent, Qt::WindowFlags f); + explicit QFileDialog(QWidget *parent = 0, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &filter = QString()); + ~QFileDialog(); + + void setDirectory(const QString &directory); + inline void setDirectory(const QDir &directory); + QDir directory() const; + + void selectFile(const QString &filename); + QStringList selectedFiles() const; + +#ifdef QT_DEPRECATED + QT_DEPRECATED void setFilter(const QString &filter); + QT_DEPRECATED void setFilters(const QStringList &filters); + QT_DEPRECATED QStringList filters() const; + QT_DEPRECATED void selectFilter(const QString &filter); + QT_DEPRECATED QString selectedFilter() const; +#endif + void setNameFilterDetailsVisible(bool enabled); + bool isNameFilterDetailsVisible() const; + + void setNameFilter(const QString &filter); + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + void selectNameFilter(const QString &filter); + QString selectedNameFilter() const; + + QDir::Filters filter() const; + void setFilter(QDir::Filters filters); + + void setViewMode(ViewMode mode); + ViewMode viewMode() const; + + void setFileMode(FileMode mode); + FileMode fileMode() const; + + void setAcceptMode(AcceptMode mode); + AcceptMode acceptMode() const; + + void setReadOnly(bool enabled); + bool isReadOnly() const; + + void setResolveSymlinks(bool enabled); + bool resolveSymlinks() const; + + void setSidebarUrls(const QList<QUrl> &urls); + QList<QUrl> sidebarUrls() const; + + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + + void setConfirmOverwrite(bool enabled); + bool confirmOverwrite() const; + + void setDefaultSuffix(const QString &suffix); + QString defaultSuffix() const; + + void setHistory(const QStringList &paths); + QStringList history() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setLabelText(DialogLabel label, const QString &text); + QString labelText(DialogLabel label) const; + +#ifndef QT_NO_PROXYMODEL + void setProxyModel(QAbstractProxyModel *model); + QAbstractProxyModel *proxyModel() const; +#endif + + void setOption(Option option, bool on = true); + bool testOption(Option option) const; + void setOptions(Options options); + Options options() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + void setVisible(bool visible); + +Q_SIGNALS: + void fileSelected(const QString &file); + void filesSelected(const QStringList &files); + void currentChanged(const QString &path); + void directoryEntered(const QString &directory); + void filterSelected(const QString &filter); + +public: +#ifdef QT3_SUPPORT + typedef FileMode Mode; + inline QT3_SUPPORT void setMode(FileMode m) { setFileMode(m); } + inline QT3_SUPPORT FileMode mode() const { return fileMode(); } + inline QT3_SUPPORT void setDir(const QString &directory) { setDirectory(directory); } + inline QT3_SUPPORT void setDir( const QDir &directory ) { setDirectory(directory); } + QT3_SUPPORT QString selectedFile() const; +#endif + + static QString getOpenFileName(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + + static QString getSaveFileName(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + + static QString getExistingDirectory(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + Options options = ShowDirsOnly); + + static QStringList getOpenFileNames(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + +#ifdef QT3_SUPPORT + inline static QString QT3_SUPPORT getOpenFileName(const QString &dir, + const QString &filter = QString(), + QWidget *parent = 0, const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getOpenFileName(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } + + inline static QString QT3_SUPPORT getSaveFileName(const QString &dir, + const QString &filter = QString(), + QWidget *parent = 0, const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getSaveFileName(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } + + inline static QString QT3_SUPPORT getExistingDirectory(const QString &dir, + QWidget *parent = 0, + const char* name = 0, + const QString &caption = QString(), + bool dirOnly = true, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getExistingDirectory(parent, caption, dir, + Options((resolveSymlinks ? Option(0) : DontResolveSymlinks) + | (dirOnly ? ShowDirsOnly : Option(0)))); } + + inline static QStringList QT3_SUPPORT getOpenFileNames(const QString &filter, + const QString &dir = QString(), + QWidget *parent = 0, + const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } +#endif // QT3_SUPPORT + +protected: + QFileDialog(const QFileDialogArgs &args); + void done(int result); + void accept(); + void changeEvent(QEvent *e); + +private: + Q_DECLARE_PRIVATE(QFileDialog) + Q_DISABLE_COPY(QFileDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_pathChanged(const QString &)) + + Q_PRIVATE_SLOT(d_func(), void _q_navigateBackward()) + Q_PRIVATE_SLOT(d_func(), void _q_navigateForward()) + Q_PRIVATE_SLOT(d_func(), void _q_navigateToParent()) + Q_PRIVATE_SLOT(d_func(), void _q_createDirectory()) + Q_PRIVATE_SLOT(d_func(), void _q_showListView()) + Q_PRIVATE_SLOT(d_func(), void _q_showDetailsView()) + Q_PRIVATE_SLOT(d_func(), void _q_showContextMenu(const QPoint &)) + Q_PRIVATE_SLOT(d_func(), void _q_renameCurrent()) + Q_PRIVATE_SLOT(d_func(), void _q_deleteCurrent()) + Q_PRIVATE_SLOT(d_func(), void _q_showHidden()) + Q_PRIVATE_SLOT(d_func(), void _q_updateOkButton()) + Q_PRIVATE_SLOT(d_func(), void _q_currentChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_enterDirectory(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_goToDirectory(const QString &path)) + Q_PRIVATE_SLOT(d_func(), void _q_useNameFilter(int index)) + Q_PRIVATE_SLOT(d_func(), void _q_selectionChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_goToUrl(const QUrl &url)) + Q_PRIVATE_SLOT(d_func(), void _q_goHome()) + Q_PRIVATE_SLOT(d_func(), void _q_showHeader(QAction *)) + Q_PRIVATE_SLOT(d_func(), void _q_autoCompleteFileName(const QString &text)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsInserted(const QModelIndex & parent)) + Q_PRIVATE_SLOT(d_func(), void _q_fileRenamed(const QString &path, + const QString oldName, const QString newName)) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif +}; + +inline void QFileDialog::setDirectory(const QDir &adirectory) +{ setDirectory(adirectory.absolutePath()); } + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFileDialog::Options) + +#endif // QT_NO_FILEDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILEDIALOG_H diff --git a/src/widgets/dialogs/qfiledialog.ui b/src/widgets/dialogs/qfiledialog.ui new file mode 100644 index 0000000000..01a49f975e --- /dev/null +++ b/src/widgets/dialogs/qfiledialog.ui @@ -0,0 +1,356 @@ +<ui version="4.0" > + <comment>********************************************************************* +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +*********************************************************************</comment> + <class>QFileDialog</class> + <widget class="QDialog" name="QFileDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>521</width> + <height>316</height> + </rect> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QGridLayout" > + <item row="0" column="0" > + <widget class="QLabel" name="lookInLabel" > + <property name="text" > + <string>Look in:</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2" > + <layout class="QHBoxLayout" > + <item> + <widget class="QFileDialogComboBox" name="lookInCombo" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Ignored" > + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>50</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="backButton" > + <property name="toolTip" > + <string>Back</string> + </property> + <property name="accessibleName"> + <string>Back</string> + </property> + <property name="accessibleDescription"> + <string>Go back</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="forwardButton" > + <property name="toolTip" > + <string>Forward</string> + </property> + <property name="accessibleName"> + <string>Forward</string> + </property> + <property name="accessibleDescription"> + <string>Go forward</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toParentButton" > + <property name="toolTip" > + <string>Parent Directory</string> + </property> + <property name="accessibleName"> + <string>Parent Directory</string> + </property> + <property name="accessibleDescription"> + <string>Go to the parent directory</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="newFolderButton" > + <property name="toolTip" > + <string>Create New Folder</string> + </property> + <property name="accessibleName"> + <string>Create New Folder</string> + </property> + <property name="accessibleDescription"> + <string>Create a New Folder</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="listModeButton" > + <property name="toolTip" > + <string>List View</string> + </property> + <property name="accessibleName"> + <string>List View</string> + </property> + <property name="accessibleDescription"> + <string>Change to list view mode</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="detailModeButton" > + <property name="toolTip" > + <string>Detail View</string> + </property> + <property name="accessibleName"> + <string>Detail View</string> + </property> + <property name="accessibleDescription"> + <string>Change to detail view mode</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0" colspan="3" > + <widget class="QSplitter" name="splitter" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Expanding" hsizetype="Expanding" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <widget class="QSidebar" name="sidebar" /> + <widget class="QFrame" name="frame" > + <property name="frameShape" > + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="leftMargin" > + <number>0</number> + </property> + <property name="topMargin" > + <number>0</number> + </property> + <property name="rightMargin" > + <number>0</number> + </property> + <property name="bottomMargin" > + <number>0</number> + </property> + <item> + <widget class="QStackedWidget" name="stackedWidget" > + <property name="currentIndex" > + <number>0</number> + </property> + <widget class="QWidget" name="page" > + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="leftMargin" > + <number>0</number> + </property> + <property name="topMargin" > + <number>0</number> + </property> + <property name="rightMargin" > + <number>0</number> + </property> + <property name="bottomMargin" > + <number>0</number> + </property> + <item> + <widget class="QFileDialogListView" name="listView" /> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2" > + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="leftMargin" > + <number>0</number> + </property> + <property name="topMargin" > + <number>0</number> + </property> + <property name="rightMargin" > + <number>0</number> + </property> + <property name="bottomMargin" > + <number>0</number> + </property> + <item> + <widget class="QFileDialogTreeView" name="treeView" /> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="2" column="0" > + <widget class="QLabel" name="fileNameLabel" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Preferred" hsizetype="Minimum" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="2" column="1" > + <widget class="QFileDialogLineEdit" name="fileNameEdit" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Expanding" > + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item rowspan="2" row="2" column="2" > + <widget class="QDialogButtonBox" name="buttonBox" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="standardButtons" > + <set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="3" column="0" > + <widget class="QLabel" name="fileTypeLabel" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Preferred" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" > + <string>Files of type:</string> + </property> + </widget> + </item> + <item row="3" column="1" > + <widget class="QComboBox" name="fileTypeCombo" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Expanding" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QFileDialogTreeView</class> + <extends>QTreeView</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogListView</class> + <extends>QListView</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QSidebar</class> + <extends>QListWidget</extends> + <header>qsidebar_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogLineEdit</class> + <extends>QLineEdit</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogComboBox</class> + <extends>QComboBox</extends> + <header>qfiledialog_p.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>lookInCombo</tabstop> + <tabstop>backButton</tabstop> + <tabstop>forwardButton</tabstop> + <tabstop>toParentButton</tabstop> + <tabstop>newFolderButton</tabstop> + <tabstop>listModeButton</tabstop> + <tabstop>detailModeButton</tabstop> + <tabstop>sidebar</tabstop> + <tabstop>listView</tabstop> + <tabstop>fileNameEdit</tabstop> + <tabstop>fileTypeCombo</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>treeView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/src/widgets/dialogs/qfiledialog_embedded.ui b/src/widgets/dialogs/qfiledialog_embedded.ui new file mode 100644 index 0000000000..f067d31f62 --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_embedded.ui @@ -0,0 +1,340 @@ +<ui version="4.0" > + <comment>********************************************************************* +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +*********************************************************************</comment> + <class>QFileDialog</class> + <widget class="QDialog" name="QFileDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>240</width> + <height>320</height> + </rect> + </property> + <property name="sizeGripEnabled" > + <bool>true</bool> + </property> + <layout class="QVBoxLayout" > + <item> + <widget class="QFileDialogComboBox" name="lookInCombo" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Expanding" > + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" > + <item> + <widget class="QToolButton" name="backButton" > + <property name="toolTip" > + <string>Back</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="forwardButton" > + <property name="toolTip" > + <string>Forward</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="toParentButton" > + <property name="toolTip" > + <string>Parent Directory</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="newFolderButton" > + <property name="toolTip" > + <string>Create New Folder</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="listModeButton" > + <property name="toolTip" > + <string>List View</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="detailModeButton" > + <property name="toolTip" > + <string>Detail View</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSplitter" name="splitter" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Expanding" hsizetype="Expanding" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <widget class="QSidebar" name="sidebar" /> + <widget class="QFrame" name="frame" > + <property name="frameShape" > + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow" > + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QStackedWidget" name="stackedWidget" > + <property name="currentIndex" > + <number>0</number> + </property> + <widget class="QWidget" name="page" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>108</width> + <height>164</height> + </rect> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QFileDialogListView" name="listView" /> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>100</width> + <height>30</height> + </rect> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QFileDialogTreeView" name="treeView" /> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QGridLayout" > + <item row="0" column="0" > + <widget class="QFileDialogLineEdit" name="fileNameEdit" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Expanding" > + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item rowspan="2" row="0" column="1" > + <widget class="QDialogButtonBox" name="buttonBox" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="standardButtons" > + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QComboBox" name="fileTypeCombo" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Expanding" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="fileNameLabel" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Fixed" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize" > + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="maximumSize" > + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="fileTypeLabel" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Fixed" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize" > + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text" > + <string>Files of type:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lookInLabel" > + <property name="enabled" > + <bool>false</bool> + </property> + <property name="sizePolicy" > + <sizepolicy vsizetype="Fixed" hsizetype="Fixed" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize" > + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text" > + <string>Look in:</string> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QFileDialogTreeView</class> + <extends>QTreeView</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogListView</class> + <extends>QListView</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QSidebar</class> + <extends>QListWidget</extends> + <header>qsidebar_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogLineEdit</class> + <extends>QLineEdit</extends> + <header>qfiledialog_p.h</header> + </customwidget> + <customwidget> + <class>QFileDialogComboBox</class> + <extends>QComboBox</extends> + <header>qfiledialog_p.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>lookInCombo</tabstop> + <tabstop>backButton</tabstop> + <tabstop>forwardButton</tabstop> + <tabstop>toParentButton</tabstop> + <tabstop>newFolderButton</tabstop> + <tabstop>listModeButton</tabstop> + <tabstop>detailModeButton</tabstop> + <tabstop>sidebar</tabstop> + <tabstop>listView</tabstop> + <tabstop>fileNameEdit</tabstop> + <tabstop>fileTypeCombo</tabstop> + <tabstop>buttonBox</tabstop> + <tabstop>treeView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/src/widgets/dialogs/qfiledialog_mac.mm b/src/widgets/dialogs/qfiledialog_mac.mm new file mode 100644 index 0000000000..885ce77c52 --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_mac.mm @@ -0,0 +1,1157 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +/***************************************************************************** + QFileDialog debug facilities + *****************************************************************************/ +//#define DEBUG_FILEDIALOG_FILTERS + +#include <qapplication.h> +#include <private/qapplication_p.h> +#include <private/qfiledialog_p.h> +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <qregexp.h> +#include <qbuffer.h> +#include <qdebug.h> +#include <qstringlist.h> +#include <qaction.h> +#include <qtextcodec.h> +#include <qvarlengtharray.h> +#include <qdesktopwidget.h> +#include <stdlib.h> +#include <qabstracteventdispatcher.h> +#import <AppKit/NSSavePanel.h> +#include "ui_qfiledialog.h" + +QT_BEGIN_NAMESPACE + +extern QStringList qt_make_filter_list(const QString &filter); // qfiledialog.cpp +extern QStringList qt_clean_filter_list(const QString &filter); // qfiledialog.cpp +extern const char *qt_file_dialog_filter_reg_exp; // qfiledialog.cpp +extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm + +QT_END_NAMESPACE + +QT_FORWARD_DECLARE_CLASS(QFileDialogPrivate) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QStringList) +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QFileInfo) +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate); + +@interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + : NSObject<NSOpenSavePanelDelegate> +#else + : NSObject +#endif +{ + @public + NSOpenPanel *mOpenPanel; + NSSavePanel *mSavePanel; + NSView *mAccessoryView; + NSPopUpButton *mPopUpButton; + NSTextField *mTextField; + QFileDialogPrivate *mPriv; + NSString *mCurrentDir; + bool mConfirmOverwrite; + int mReturnCode; + + QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode) mAcceptMode; + QT_PREPEND_NAMESPACE(QDir::Filters) *mQDirFilter; + QT_PREPEND_NAMESPACE(QFileDialog::FileMode) mFileMode; + QT_PREPEND_NAMESPACE(QFileDialog::Options) *mFileOptions; + + QString *mLastFilterCheckPath; + QString *mCurrentSelection; + QStringList *mQDirFilterEntryList; + QStringList *mNameFilterDropDownList; + QStringList *mSelectedNameFilter; +} + +- (NSString *)strip:(const QString &)label; +- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename; +- (void)filterChanged:(id)sender; +- (void)showModelessPanel; +- (BOOL)runApplicationModalPanel; +- (void)showWindowModalSheet:(QWidget *)docWidget; +- (void)updateProperties; +- (QStringList)acceptableExtensionsForSave; +- (QString)removeExtensions:(const QString &)filter; +- (void)createTextField; +- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails; +- (QStringList)findStrippedFilterWithVisualFilterName:(QString)name; +- (void)createAccessory; + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) + +- (id)initWithAcceptMode:(QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode))acceptMode + title:(const QString &)title + hideNameFilterDetails:(bool)hideNameFilterDetails + qDirFilter:(QT_PREPEND_NAMESPACE(QDir::Filters))qDirFilter + fileOptions:(QT_PREPEND_NAMESPACE(QFileDialog::Options))fileOptions + fileMode:(QT_PREPEND_NAMESPACE(QFileDialog::FileMode))fileMode + selectFile:(const QString &)selectFile + confirmOverwrite:(bool)confirm + priv:(QFileDialogPrivate *)priv +{ + self = [super init]; + + mAcceptMode = acceptMode; + if (mAcceptMode == QT_PREPEND_NAMESPACE(QFileDialog::AcceptOpen)){ + mOpenPanel = [NSOpenPanel openPanel]; + mSavePanel = mOpenPanel; + } else { + mSavePanel = [NSSavePanel savePanel]; + mOpenPanel = 0; + } + + [mSavePanel setLevel:NSModalPanelWindowLevel]; + [mSavePanel setDelegate:self]; + mQDirFilter = new QT_PREPEND_NAMESPACE(QDir::Filters)(qDirFilter); + mFileOptions = new QT_PREPEND_NAMESPACE(QFileDialog::Options)(fileOptions); + mFileMode = fileMode; + mConfirmOverwrite = confirm; + mReturnCode = -1; + mPriv = priv; + mLastFilterCheckPath = new QString; + mQDirFilterEntryList = new QStringList; + mNameFilterDropDownList = new QStringList(priv->nameFilters); + QString selectedVisualNameFilter = priv->qFileDialogUi->fileTypeCombo->currentText(); + mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]); + + QFileInfo sel(selectFile); + if (sel.isDir()){ + mCurrentDir = [qt_mac_QStringToNSString(sel.absoluteFilePath()) retain]; + mCurrentSelection = new QString; + } else { + mCurrentDir = [qt_mac_QStringToNSString(sel.absolutePath()) retain]; + mCurrentSelection = new QString(sel.absoluteFilePath()); + } + + [mSavePanel setTitle:qt_mac_QStringToNSString(title)]; + [self createPopUpButton:selectedVisualNameFilter hideDetails:hideNameFilterDetails]; + [self createTextField]; + [self createAccessory]; + [mSavePanel setAccessoryView:mNameFilterDropDownList->size() > 1 ? mAccessoryView : nil]; + + if (mPriv){ + [mSavePanel setPrompt:[self strip:mPriv->acceptLabel]]; + if (mPriv->fileNameLabelExplicitlySat) + [mSavePanel setNameFieldLabel:[self strip:mPriv->qFileDialogUi->fileNameLabel->text()]]; + } + + [self updateProperties]; + [mSavePanel retain]; + return self; +} + +- (void)dealloc +{ + delete mQDirFilter; + delete mFileOptions; + delete mLastFilterCheckPath; + delete mQDirFilterEntryList; + delete mNameFilterDropDownList; + delete mSelectedNameFilter; + delete mCurrentSelection; + + [mSavePanel orderOut:mSavePanel]; + [mSavePanel setAccessoryView:nil]; + [mPopUpButton release]; + [mTextField release]; + [mAccessoryView release]; + [mSavePanel setDelegate:nil]; + [mSavePanel release]; + [mCurrentDir release]; + [super dealloc]; +} + +- (NSString *)strip:(const QString &)label +{ + QAction a(label, 0); + return qt_mac_QStringToNSString(a.iconText()); +} + +- (void)closePanel +{ + *mCurrentSelection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]); + [mSavePanel close]; +} + +- (void)showModelessPanel +{ + if (mOpenPanel){ + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + [mOpenPanel + beginForDirectory:mCurrentDir + file:selectable ? filename : nil + types:nil + modelessDelegate:self + didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) + contextInfo:nil]; + } +} + +- (BOOL)runApplicationModalPanel +{ + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + mReturnCode = [mSavePanel + runModalForDirectory:mCurrentDir + file:selectable ? filename : @"untitled"]; + + QAbstractEventDispatcher::instance()->interrupt(); + return (mReturnCode == NSOKButton); +} + +- (QT_PREPEND_NAMESPACE(QDialog::DialogCode))dialogResultCode +{ + return (mReturnCode == NSOKButton) ? QT_PREPEND_NAMESPACE(QDialog::Accepted) : QT_PREPEND_NAMESPACE(QDialog::Rejected); +} + +- (void)showWindowModalSheet:(QWidget *)docWidget +{ + Q_UNUSED(docWidget); + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + [mSavePanel + beginSheetForDirectory:mCurrentDir + file:selectable ? filename : nil +#ifdef QT_MAC_USE_COCOA + modalForWindow:QT_PREPEND_NAMESPACE(qt_mac_window_for)(docWidget) +#else + modalForWindow:nil +#endif + modalDelegate:self + didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) + contextInfo:nil]; +} + +- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename +{ + Q_UNUSED(sender); + + if ([filename length] == 0) + return NO; + + // Always accept directories regardless of their names (unless it is a bundle): + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDir] && isDir) { + if ([mSavePanel treatsFilePackagesAsDirectories] == NO) { + if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO) + return YES; + } + } + + QString qtFileName = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)(filename); + QFileInfo info(qtFileName.normalized(QT_PREPEND_NAMESPACE(QString::NormalizationForm_C))); + QString path = info.absolutePath(); + if (path != *mLastFilterCheckPath){ + *mLastFilterCheckPath = path; + *mQDirFilterEntryList = info.dir().entryList(*mQDirFilter); + } + // Check if the QDir filter accepts the file: + if (!mQDirFilterEntryList->contains(info.fileName())) + return NO; + + // No filter means accept everything + if (mSelectedNameFilter->isEmpty()) + return YES; + // Check if the current file name filter accepts the file: + for (int i=0; i<mSelectedNameFilter->size(); ++i) { + if (QDir::match(mSelectedNameFilter->at(i), qtFileName)) + return YES; + } + return NO; +} + +- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag +{ + Q_UNUSED(sender); + if (!okFlag) + return filename; + if (mConfirmOverwrite) + return filename; + + // User has clicked save, and no overwrite confirmation should occur. + // To get the latter, we need to change the name we return (hence the prefix): + return [@"___qt_very_unlikely_prefix_" stringByAppendingString:filename]; +} + +- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails +{ + [mPopUpButton removeAllItems]; + *mNameFilterDropDownList = filters; + if (filters.size() > 0){ + for (int i=0; i<filters.size(); ++i) { + QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i); + [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)]; + } + [mPopUpButton selectItemAtIndex:0]; + [mSavePanel setAccessoryView:mAccessoryView]; + } else + [mSavePanel setAccessoryView:nil]; + + [self filterChanged:self]; +} + +- (void)filterChanged:(id)sender +{ + // This mDelegate function is called when the _name_ filter changes. + Q_UNUSED(sender); + QString selection = mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]); + *mSelectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection]; + [mSavePanel validateVisibleColumns]; + [self updateProperties]; + if (mPriv) + mPriv->QNSOpenSavePanelDelegate_filterSelected([mPopUpButton indexOfSelectedItem]); +} + +- (QString)currentNameFilter +{ + return mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]); +} + +- (QStringList)selectedFiles +{ + if (mOpenPanel) + return QT_PREPEND_NAMESPACE(qt_mac_NSArrayToQStringList)([mOpenPanel filenames]); + else{ + QStringList result; + QString filename = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]); + result << filename.remove(QLatin1String("___qt_very_unlikely_prefix_")); + return result; + } +} + +- (void)updateProperties +{ + // Call this functions if mFileMode, mFileOptions, + // mNameFilterDropDownList or mQDirFilter changes. + // The savepanel does not contain the neccessary functions for this. + bool chooseFilesOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFile) + || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles); + bool chooseDirsOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::Directory) + || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::DirectoryOnly) + || *mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ShowDirsOnly); + + [mOpenPanel setCanChooseFiles:!chooseDirsOnly]; + [mOpenPanel setCanChooseDirectories:!chooseFilesOnly]; + [mSavePanel setCanCreateDirectories:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ReadOnly))]; + [mOpenPanel setAllowsMultipleSelection:(mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles))]; + [mOpenPanel setResolvesAliases:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::DontResolveSymlinks))]; + + QStringList ext = [self acceptableExtensionsForSave]; + if (mPriv && !ext.isEmpty() && !mPriv->defaultSuffix.isEmpty()) + ext.prepend(mPriv->defaultSuffix); + [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : QT_PREPEND_NAMESPACE(qt_mac_QStringListToNSMutableArray(ext))]; + + if ([mSavePanel isVisible]) + [mOpenPanel validateVisibleColumns]; +} + +- (void)panelSelectionDidChange:(id)sender +{ + Q_UNUSED(sender); + if (mPriv) { + QString selection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString([mSavePanel filename])); + if (selection != mCurrentSelection) { + *mCurrentSelection = selection; + mPriv->QNSOpenSavePanelDelegate_selectionChanged(selection); + } + } +} + +- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + Q_UNUSED(panel); + Q_UNUSED(contextInfo); + mReturnCode = returnCode; + if (mPriv) + mPriv->QNSOpenSavePanelDelegate_panelClosed(returnCode == NSOKButton); +} + +- (void)panel:(id)sender directoryDidChange:(NSString *)path +{ + Q_UNUSED(sender); + if (!mPriv) + return; + if ([path isEqualToString:mCurrentDir]) + return; + + [mCurrentDir release]; + mCurrentDir = [path retain]; + mPriv->QNSOpenSavePanelDelegate_directoryEntered(QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString(mCurrentDir))); +} + +/* + Returns a list of extensions (e.g. "png", "jpg", "gif") + for the current name filter. If a filter do not conform + to the format *.xyz or * or *.*, an empty list + is returned meaning accept everything. +*/ +- (QStringList)acceptableExtensionsForSave +{ + QStringList result; + for (int i=0; i<mSelectedNameFilter->count(); ++i) { + const QString &filter = mSelectedNameFilter->at(i); + if (filter.startsWith(QLatin1String("*.")) + && !filter.contains(QLatin1Char('?')) + && filter.count(QLatin1Char('*')) == 1) { + result += filter.mid(2); + } else { + return QStringList(); // Accept everything + } + } + return result; +} + +- (QString)removeExtensions:(const QString &)filter +{ + QRegExp regExp(QT_PREPEND_NAMESPACE(QString::fromLatin1)(QT_PREPEND_NAMESPACE(qt_file_dialog_filter_reg_exp))); + if (regExp.indexIn(filter) != -1) + return regExp.cap(1).trimmed(); + return filter; +} + +- (void)createTextField +{ + NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } }; + mTextField = [[NSTextField alloc] initWithFrame:textRect]; + [[mTextField cell] setFont:[NSFont systemFontOfSize: + [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + [mTextField setAlignment:NSRightTextAlignment]; + [mTextField setEditable:false]; + [mTextField setSelectable:false]; + [mTextField setBordered:false]; + [mTextField setDrawsBackground:false]; + if (mPriv){ + [mTextField setStringValue:[self strip:mPriv->qFileDialogUi->fileTypeLabel->text()]]; + } else + [mTextField setStringValue:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(QT_PREPEND_NAMESPACE(QFileDialog::tr)("Files of type:"))]; +} + +- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails +{ + NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } }; + mPopUpButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO]; + [mPopUpButton setTarget:self]; + [mPopUpButton setAction:@selector(filterChanged:)]; + + QStringList *filters = mNameFilterDropDownList; + if (filters->size() > 0){ + for (int i=0; i<mNameFilterDropDownList->size(); ++i) { + QString filter = hideDetails ? [self removeExtensions:filters->at(i)] : filters->at(i); + [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)]; + if (filters->at(i).startsWith(selectedFilter)) + [mPopUpButton selectItemAtIndex:i]; + } + } +} + +- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name +{ + for (int i=0; i<mNameFilterDropDownList->size(); ++i) { + if (mNameFilterDropDownList->at(i).startsWith(name)) + return qt_clean_filter_list(mNameFilterDropDownList->at(i)); + } + return QStringList(); +} + +- (void)createAccessory +{ + NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } }; + mAccessoryView = [[NSView alloc] initWithFrame:accessoryRect]; + [mAccessoryView addSubview:mTextField]; + [mAccessoryView addSubview:mPopUpButton]; +} + +@end + +QT_BEGIN_NAMESPACE + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath) +{ + emit q_func()->currentChanged(newPath); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_panelClosed(bool accepted) +{ + if (accepted) + q_func()->accept(); + else + q_func()->reject(); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir) +{ + setLastVisitedDirectory(newDir); + emit q_func()->directoryEntered(newDir); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_filterSelected(int menuIndex) +{ + emit q_func()->filterSelected(nameFilters.at(menuIndex)); +} + +extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp +extern void qt_mac_to_pascal_string(QString s, Str255 str, TextEncoding encoding=0, int len=-1); // qglobal.cpp + +void QFileDialogPrivate::setDirectory_sys(const QString &directory) +{ +#ifndef QT_MAC_USE_COCOA + if (directory == mCurrentLocation) + return; + mCurrentLocation = directory; + emit q_func()->directoryEntered(mCurrentLocation); + + FSRef fsRef; + if (qt_mac_create_fsref(directory, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc); + } +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + [delegate->mSavePanel setDirectory:qt_mac_QStringToNSString(directory)]; +#endif +} + +QString QFileDialogPrivate::directory_sys() const +{ +#ifndef QT_MAC_USE_COCOA + return mCurrentLocation; +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + return qt_mac_NSStringToQString([delegate->mSavePanel directory]); +#endif +} + +void QFileDialogPrivate::selectFile_sys(const QString &filename) +{ + QString filePath = filename; + if (QDir::isRelativePath(filePath)) + filePath = QFileInfo(directory_sys(), filePath).filePath(); + +#ifndef QT_MAC_USE_COCOA + // Update the selection list immidiatly, so + // subsequent calls to selectedFiles() gets correct: + mCurrentSelectionList.clear(); + mCurrentSelectionList << filename; + if (mCurrentSelection != filename){ + mCurrentSelection = filename; + emit q_func()->currentChanged(mCurrentSelection); + } + + AEDescList descList; + if (AECreateList(0, 0, false, &descList) != noErr) + return; + + FSRef fsRef; + if (qt_mac_create_fsref(filePath, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr){ + if (AEPutDesc(&descList, 0, &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetSelection, (void*)&descList); + } + } + + // Type the file name into the save dialog's text field: + UInt8 *strBuffer = (UInt8 *)malloc(1024); + qt_mac_to_pascal_string(QFileInfo(filename).fileName(), strBuffer); + NavCustomControl(mDialog, kNavCtlSetEditFileName, strBuffer); + free(strBuffer); +#else + // There seems to no way to select a file once the dialog is running. + // So do the next best thing, set the file's directory: + setDirectory_sys(QFileInfo(filePath).absolutePath()); +#endif +} + +QStringList QFileDialogPrivate::selectedFiles_sys() const +{ +#ifndef QT_MAC_USE_COCOA + if (q_func()->acceptMode() == QFileDialog::AcceptOpen){ + return mCurrentSelectionList; + } else { + return QStringList() << mCurrentLocation + QLatin1Char('/') + + QCFString::toQString(NavDialogGetSaveFileName(mDialog)); + } +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + return [delegate selectedFiles]; +#endif +} + +void QFileDialogPrivate::setNameFilters_sys(const QStringList &filters) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(filters); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + bool hideDetails = q_func()->testOption(QFileDialog::HideNameFilterDetails); + [delegate setNameFilters:filters hideDetails:hideDetails]; +#endif +} + +void QFileDialogPrivate::setFilter_sys() +{ +#ifndef QT_MAC_USE_COCOA +#else + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + *(delegate->mQDirFilter) = model->filter(); + delegate->mFileMode = fileMode; + [delegate->mSavePanel setTitle:qt_mac_QStringToNSString(q->windowTitle())]; + [delegate->mSavePanel setPrompt:[delegate strip:acceptLabel]]; + if (fileNameLabelExplicitlySat) + [delegate->mSavePanel setNameFieldLabel:[delegate strip:qFileDialogUi->fileNameLabel->text()]]; + + [delegate updateProperties]; +#endif +} + +void QFileDialogPrivate::selectNameFilter_sys(const QString &filter) +{ + int index = nameFilters.indexOf(filter); + if (index != -1) { +#ifndef QT_MAC_USE_COCOA + NavMenuItemSpec navSpec; + bzero(&navSpec, sizeof(NavMenuItemSpec)); + navSpec.menuType = index; + NavCustomControl(mDialog, kNavCtlSelectCustomType, &navSpec); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + [delegate->mPopUpButton selectItemAtIndex:index]; + [delegate filterChanged:nil]; +#endif + } +} + +QString QFileDialogPrivate::selectedNameFilter_sys() const +{ +#ifndef QT_MAC_USE_COCOA + int index = filterInfo.currentSelection; +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + int index = [delegate->mPopUpButton indexOfSelectedItem]; +#endif + return index != -1 ? nameFilters.at(index) : QString(); +} + +void QFileDialogPrivate::deleteNativeDialog_sys() +{ +#ifndef QT_MAC_USE_COCOA + if (mDialog) + NavDialogDispose(mDialog); + mDialog = 0; + mDialogStarted = false; +#else + QMacCocoaAutoReleasePool pool; + [reinterpret_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate) release]; + mDelegate = 0; +#endif + nativeDialogInUse = false; +} + +bool QFileDialogPrivate::setVisible_sys(bool visible) +{ + Q_Q(QFileDialog); + if (!visible == q->isHidden()) + return false; + + if (q->windowFlags() & Qt::WindowStaysOnTopHint) { + // The native file dialog tries all it can to stay + // on the NSModalPanel level. And it might also show + // its own "create directory" dialog that we cannot control. + // So we need to use the non-native version in this case... + return false; + } + +#ifndef QT_MAC_USE_COCOA + return visible ? showCarbonNavServicesDialog() : hideCarbonNavServicesDialog(); +#else + return visible ? showCocoaFilePanel() : hideCocoaFilePanel(); +#endif +} + +#ifndef QT_MAC_USE_COCOA +Boolean QFileDialogPrivate::qt_mac_filedialog_filter_proc(AEDesc *theItem, void *info, + void *data, NavFilterModes) +{ + QFileDialogPrivate *fileDialogPrivate = static_cast<QFileDialogPrivate *>(data); + + if (!fileDialogPrivate || fileDialogPrivate->filterInfo.filters.isEmpty() + || (fileDialogPrivate->filterInfo.currentSelection < 0 + && fileDialogPrivate->filterInfo.currentSelection + >= fileDialogPrivate->filterInfo.filters.size())) + return true; + + NavFileOrFolderInfo *theInfo = static_cast<NavFileOrFolderInfo *>(info); + QString file; + QString path; + const QtMacFilterName &fn + = fileDialogPrivate->filterInfo.filters.at(fileDialogPrivate->filterInfo.currentSelection); + if (theItem->descriptorType == typeFSRef) { + FSRef ref; + AEGetDescData(theItem, &ref, sizeof(ref)); + UInt8 str_buffer[1024]; + FSRefMakePath(&ref, str_buffer, 1024); + path = QString::fromUtf8(reinterpret_cast<const char *>(str_buffer)); + int slsh = path.lastIndexOf(QLatin1Char('/')); + if (slsh != -1) + file = path.right(path.length() - slsh - 1); + else + file = path; + } + QStringList reg = fn.regexp.split(QLatin1String(";")); + for (QStringList::const_iterator it = reg.constBegin(); it != reg.constEnd(); ++it) { + QRegExp rg(*it, Qt::CaseInsensitive, QRegExp::Wildcard); +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialogPrivate::qt_mac_filedialog_filter_proc:%d, asked to filter.. %s (%s)", __LINE__, + qPrintable(file), qPrintable(*it)); +#endif + if (rg.exactMatch(file)) + return true; + } + + if (theInfo->isFolder) { + if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:qt_mac_QStringToNSString(path)]) + return false; + return true; + } + return false; +} + +void QFileDialogPrivate::qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg, + NavCBRecPtr p, NavCallBackUserData data) +{ + QFileDialogPrivate *fileDialogPrivate = static_cast<QFileDialogPrivate *>(data); + + switch(msg) { + case kNavCBPopupMenuSelect: { + NavMenuItemSpec *s = static_cast<NavMenuItemSpec *>(p->eventData.eventDataParms.param); + if (int(s->menuType) != fileDialogPrivate->filterInfo.currentSelection) { + fileDialogPrivate->filterInfo.currentSelection = s->menuType; + emit fileDialogPrivate->q_func()->filterSelected(fileDialogPrivate->nameFilters.at(s->menuType)); + } + if (fileDialogPrivate->acceptMode == QFileDialog::AcceptSave) { + QString base = QCFString::toQString(NavDialogGetSaveFileName(p->context)); + QFileInfo fi(base); + base = fi.completeBaseName(); + const QtMacFilterName &fn = fileDialogPrivate->filterInfo.filters.at( + fileDialogPrivate->filterInfo.currentSelection); + QStringList reg = fn.regexp.split(QLatin1String(";"), QString::SkipEmptyParts); + if (reg.count()) { + QString r = reg.first(); + r = r.right(r.length()-1); // Strip the * + base += r; //"." + QString::number(s->menuType); + } + NavDialogSetSaveFileName(p->context, QCFString::toCFStringRef(base)); + } +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialogPrivate::qt_mac_filedialog_event_proc:%d - Selected a filter: %ld", __LINE__, s->menuType); +#endif + break; } + case kNavCBStart:{ + fileDialogPrivate->mDialogStarted = true; + // Set selected file: + QModelIndexList indexes = fileDialogPrivate->qFileDialogUi->listView->selectionModel()->selectedRows(); + QString selected; + if (!indexes.isEmpty()) + selected = indexes.at(0).data(QFileSystemModel::FilePathRole).toString(); + else + selected = fileDialogPrivate->typedFiles().value(0); + fileDialogPrivate->selectFile_sys(selected); + fileDialogPrivate->selectNameFilter_sys(fileDialogPrivate->qFileDialogUi->fileTypeCombo->currentText()); + break; } + case kNavCBSelectEntry:{ + // Event: Current selection has changed. + QStringList prevSelectionList = fileDialogPrivate->mCurrentSelectionList; + fileDialogPrivate->mCurrentSelectionList.clear(); + QString fileNameToEmit; + + AEDescList *descList = (AEDescList *)p->eventData.eventDataParms.param; + // Get the number of files selected: + UInt8 strBuffer[1024]; + long count; + OSErr err = AECountItems(descList, &count); + if (err != noErr || !count) + break; + + for (long index=1; index<=count; ++index) { + FSRef ref; + err = AEGetNthPtr(descList, index, typeFSRef, 0, 0, &ref, sizeof(ref), 0); + if (err != noErr) + break; + FSRefMakePath(&ref, strBuffer, 1024); + QString selected = QString::fromUtf8((const char *)strBuffer); + fileDialogPrivate->mCurrentSelectionList << selected; + if (!prevSelectionList.contains(selected)) + fileNameToEmit = selected; + } + + if (!fileNameToEmit.isEmpty() && fileNameToEmit != fileDialogPrivate->mCurrentSelection) + emit fileDialogPrivate->q_func()->currentChanged(fileNameToEmit); + fileDialogPrivate->mCurrentSelection = fileNameToEmit; + break; } + case kNavCBShowDesktop: + case kNavCBNewLocation:{ + // Event: Current directory has changed. + AEDesc *desc = (AEDesc *)p->eventData.eventDataParms.param; + FSRef ref; + AEGetDescData(desc, &ref, sizeof(ref)); + UInt8 *strBuffer = (UInt8 *)malloc(1024); + FSRefMakePath(&ref, strBuffer, 1024); + QString newLocation = QString::fromUtf8((const char *)strBuffer); + free(strBuffer); + if (fileDialogPrivate->mCurrentLocation != newLocation){ + fileDialogPrivate->mCurrentLocation = newLocation; + QFileDialog::FileMode mode = fileDialogPrivate->fileMode; + if (mode == QFileDialog::AnyFile || mode == QFileDialog::ExistingFile + || mode == QFileDialog::ExistingFiles){ + // When changing directory, the current selection is cleared if + // we are supposed to be selecting files only: + if (!fileDialogPrivate->mCurrentSelection.isEmpty()){ + fileDialogPrivate->mCurrentSelectionList.clear(); + fileDialogPrivate->mCurrentSelection.clear(); + emit fileDialogPrivate->q_func()->currentChanged(fileDialogPrivate->mCurrentSelection); + } + } + fileDialogPrivate->setLastVisitedDirectory(newLocation); + emit fileDialogPrivate->q_func()->directoryEntered(newLocation); + } + break; } + case kNavCBAccept: + fileDialogPrivate->mDialogClosed = true; + fileDialogPrivate->q_func()->accept(); + break; + case kNavCBCancel: + fileDialogPrivate->mDialogClosed = true; + fileDialogPrivate->q_func()->reject(); + break; + } +} + +static QFileDialogPrivate::QtMacFilterName qt_mac_extract_filter(const QString &rawFilter, bool showDetails) +{ + QFileDialogPrivate::QtMacFilterName ret; + ret.filter = rawFilter; + QString result = rawFilter; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + int index = r.indexIn(result); + if (index >= 0) + result = r.cap(2); + + if (showDetails) { + ret.description = rawFilter; + } else { + if (index >= 0) + ret.description = r.cap(1).trimmed(); + if (ret.description.isEmpty()) + ret.description = result; + } + ret.regexp = result.replace(QLatin1Char(' '), QLatin1Char(';')); + return ret; +} + +static QList<QFileDialogPrivate::QtMacFilterName> qt_mac_make_filters_list(const QString &filter, bool showDetails) +{ +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialog:%d - Got filter (%s)", __LINE__, filter.latin1()); +#endif + + QList<QFileDialogPrivate::QtMacFilterName> ret; + QString f(filter); + if (f.isEmpty()) + f = QFileDialog::tr("All Files (*)"); + if (f.isEmpty()) + return ret; + QStringList filts = qt_make_filter_list(f); + for (QStringList::const_iterator it = filts.constBegin(); it != filts.constEnd(); ++it) { + QFileDialogPrivate::QtMacFilterName filter = qt_mac_extract_filter(*it, showDetails); +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialog:%d Split out filter (%d) '%s' '%s' [%s]", __LINE__, ret.count(), + filter->regxp.latin1(), filter->description.latin1(), (*it).latin1()); +#endif + ret.append(filter); + } + return ret; +} + +void QFileDialogPrivate::createNavServicesDialog() +{ + Q_Q(QFileDialog); + if (mDialog) + deleteNativeDialog_sys(); + + NavDialogCreationOptions navOptions; + NavGetDefaultDialogCreationOptions(&navOptions); + + // Translate QFileDialog settings into NavDialog options: + if (qt_mac_is_macsheet(q)) { + navOptions.modality = kWindowModalityWindowModal; + navOptions.parentWindow = qt_mac_window_for(q->parentWidget()); + } else if (q->windowModality() == Qt::ApplicationModal) + navOptions.modality = kWindowModalityAppModal; + else + navOptions.modality = kWindowModalityNone; + navOptions.optionFlags |= kNavSupportPackages; + if (q->testOption(QFileDialog::DontConfirmOverwrite)) + navOptions.optionFlags |= kNavDontConfirmReplacement; + if (fileMode != QFileDialog::ExistingFiles) + navOptions.optionFlags &= ~kNavAllowMultipleFiles; + + navOptions.windowTitle = QCFString::toCFStringRef(q->windowTitle()); + + navOptions.location.h = -1; + navOptions.location.v = -1; + + QWidget *parent = q->parentWidget(); + if (parent && parent->isVisible()) { + WindowClass wclass; + GetWindowClass(qt_mac_window_for(parent), &wclass); + parent = parent->window(); + QString s = parent->windowTitle(); + navOptions.clientName = QCFString::toCFStringRef(s); + } + + filterInfo.currentSelection = 0; + filterInfo.filters = qt_mac_make_filters_list(nameFilters.join(QLatin1String(";;")), q->isNameFilterDetailsVisible()); + QCFType<CFArrayRef> filterArray; + if (filterInfo.filters.size() > 1) { + int i = 0; + CFStringRef *cfstringArray = static_cast<CFStringRef *>(malloc(sizeof(CFStringRef) + * filterInfo.filters.size())); + for (i = 0; i < filterInfo.filters.size(); ++i) { + cfstringArray[i] = QCFString::toCFStringRef(filterInfo.filters.at(i).description); + } + filterArray = CFArrayCreate(kCFAllocatorDefault, + reinterpret_cast<const void **>(cfstringArray), filterInfo.filters.size(), + &kCFTypeArrayCallBacks); + navOptions.popupExtension = filterArray; + free(cfstringArray); + } + + if (q->acceptMode() == QFileDialog::AcceptSave) { + if (NavCreatePutFileDialog(&navOptions, 'cute', kNavGenericSignature, + QFileDialogPrivate::qt_mac_filedialog_event_proc, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } else if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) { + if (NavCreateChooseFolderDialog(&navOptions, + QFileDialogPrivate::qt_mac_filedialog_event_proc, 0, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } else { + if (NavCreateGetFileDialog(&navOptions, 0, + QFileDialogPrivate::qt_mac_filedialog_event_proc, 0, + QFileDialogPrivate::qt_mac_filedialog_filter_proc, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } + + // Set start-up directory: + if (mCurrentLocation.isEmpty()) + mCurrentLocation = rootPath(); + FSRef fsRef; + if (qt_mac_create_fsref(mCurrentLocation, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc); + } +} + +bool QFileDialogPrivate::showCarbonNavServicesDialog() +{ + Q_Q(QFileDialog); + if (q->acceptMode() == QFileDialog::AcceptSave && q->windowModality() == Qt::NonModal) + return false; // cannot do native no-modal save dialogs. + createNavServicesDialog(); + mDialogClosed = false; + if (q->windowModality() != Qt::ApplicationModal) + NavDialogRun(mDialog); + return true; +} + +bool QFileDialogPrivate::hideCarbonNavServicesDialog() +{ + if (!mDialogClosed){ + mDialogClosed = true; + NavCustomControl(mDialog, kNavCtlCancel, 0); + } + return true; +} + +#else // Cocoa + +void QFileDialogPrivate::createNSOpenSavePanelDelegate() +{ + Q_Q(QFileDialog); + if (mDelegate) + return; + + bool selectDir = q->selectedFiles().isEmpty(); + QString selection(selectDir ? q->directory().absolutePath() : q->selectedFiles().value(0)); + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) alloc] + initWithAcceptMode:acceptMode + title:q->windowTitle() + hideNameFilterDetails:q->testOption(QFileDialog::HideNameFilterDetails) + qDirFilter:model->filter() + fileOptions:opts + fileMode:fileMode + selectFile:selection + confirmOverwrite:!q->testOption(QFileDialog::DontConfirmOverwrite) + priv:this]; + + mDelegate = delegate; +} + +bool QFileDialogPrivate::showCocoaFilePanel() +{ + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + createNSOpenSavePanelDelegate(); + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + if (qt_mac_is_macsheet(q)) + [delegate showWindowModalSheet:q->parentWidget()]; + else + [delegate showModelessPanel]; + return true; +} + +bool QFileDialogPrivate::hideCocoaFilePanel() +{ + if (!mDelegate){ + // Nothing to do. We return false to leave the question + // open regarding whether or not to go native: + return false; + } else { + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + [delegate closePanel]; + // Even when we hide it, we are still using a + // native dialog, so return true: + return true; + } +} + +#endif + +void QFileDialogPrivate::mac_nativeDialogModalHelp() +{ + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most QEventLoop has been + // interrupted, and the second-most event loop has not yet been reactivated (regardless + // if [NSApp run] is still on the stack)), showing a native modal dialog will fail. + if (nativeDialogInUse){ + Q_Q(QFileDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +void QFileDialogPrivate::_q_macRunNativeAppModalPanel() +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); +#ifndef QT_MAC_USE_COCOA + NavDialogRun(mDialog); +#else + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + [delegate runApplicationModalPanel]; + dialogResultCode_sys() == QDialog::Accepted ? q->accept() : q->reject(); +#endif +} + +QDialog::DialogCode QFileDialogPrivate::dialogResultCode_sys() +{ +#ifndef QT_MAC_USE_COCOA + NavUserAction result = NavDialogGetUserAction(mDialog); + if (result == kNavUserActionCancel || result == kNavUserActionNone) + return QDialog::Rejected; + else + return QDialog::Accepted; +#else + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate); + return [delegate dialogResultCode]; +#endif +} + + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h new file mode 100644 index 0000000000..6a534bb60f --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_p.h @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEDIALOG_P_H +#define QFILEDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_FILEDIALOG + +#include "qfiledialog.h" +#include "private/qdialog_p.h" +#include "qplatformdefs.h" + +#include "qfilesystemmodel_p.h" +#include <qlistview.h> +#include <qtreeview.h> +#include <qcombobox.h> +#include <qtoolbutton.h> +#include <qlabel.h> +#include <qevent.h> +#include <qlineedit.h> +#include <qurl.h> +#include <qstackedwidget.h> +#include <qdialogbuttonbox.h> +#include <qabstractproxymodel.h> +#include <qcompleter.h> +#include <qpointer.h> +#include <qdebug.h> +#include "qsidebar_p.h" +#include "qfscompleter_p.h" +#include "private/qguiplatformplugin_p.h" + + +#if defined (Q_OS_UNIX) +#include <unistd.h> +#endif + +QT_BEGIN_NAMESPACE + +class QFileDialogListView; +class QFileDialogTreeView; +class QFileDialogLineEdit; +class QGridLayout; +class QCompleter; +class QHBoxLayout; +class Ui_QFileDialog; + + +struct QFileDialogArgs +{ + QFileDialogArgs() : parent(0), mode(QFileDialog::AnyFile) {} + + QWidget *parent; + QString caption; + QString directory; + QString selection; + QString filter; + QFileDialog::FileMode mode; + QFileDialog::Options options; +}; + +#define UrlRole (Qt::UserRole + 1) + +class Q_AUTOTEST_EXPORT QFileDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QFileDialog) + +public: + QFileDialogPrivate(); + + void createToolButtons(); + void createMenuActions(); + void createWidgets(); + + void init(const QString &directory = QString(), const QString &nameFilter = QString(), + const QString &caption = QString()); + bool itemViewKeyboardEvent(QKeyEvent *event); + QString getEnvironmentVariable(const QString &string); + static QString workingDirectory(const QString &path); + static QString initialSelection(const QString &path); + QStringList typedFiles() const; + QStringList addDefaultSuffixToFiles(const QStringList filesToFix) const; + bool removeDirectory(const QString &path); + + inline QModelIndex mapToSource(const QModelIndex &index) const; + inline QModelIndex mapFromSource(const QModelIndex &index) const; + inline QModelIndex rootIndex() const; + inline void setRootIndex(const QModelIndex &index) const; + inline QModelIndex select(const QModelIndex &index) const; + inline QString rootPath() const; + + QLineEdit *lineEdit() const; + + int maxNameLength(const QString &path) { +#if defined(Q_OS_UNIX) + return ::pathconf(QFile::encodeName(path).data(), _PC_NAME_MAX); +#elif defined(Q_OS_WIN) +#ifndef Q_OS_WINCE + DWORD maxLength; + QString drive = path.left(3); + if (::GetVolumeInformation(reinterpret_cast<const wchar_t *>(drive.utf16()), NULL, 0, NULL, &maxLength, NULL, NULL, 0) == FALSE) + return -1; + return maxLength; +#else + Q_UNUSED(path); + return MAX_PATH; +#endif //Q_OS_WINCE +#else + Q_UNUSED(path); +#endif + return -1; + } + + QString basename(const QString &path) const + { + int separator = QDir::toNativeSeparators(path).lastIndexOf(QDir::separator()); + if (separator != -1) + return path.mid(separator + 1); + return path; + } + + QDir::Filters filterForMode(QDir::Filters filters) const + { + if (fileMode == QFileDialog::DirectoryOnly) { + filters |= QDir::Drives | QDir::AllDirs | QDir::Dirs; + filters &= ~QDir::Files; + } else { + filters |= QDir::Drives | QDir::AllDirs | QDir::Files | QDir::Dirs; + } + return filters; + } + + QAbstractItemView *currentView() const; + + static inline QString toInternal(const QString &path) + { +#if defined(Q_FS_FAT) || defined(Q_OS_OS2EMX) || defined(Q_OS_SYMBIAN) + QString n(path); + for (int i = 0; i < (int)n.length(); ++i) + if (n[i] == QLatin1Char('\\')) n[i] = QLatin1Char('/'); +#if defined(Q_OS_WINCE) + if ((n.size() > 1) && (n.startsWith(QLatin1String("//")))) + n = n.mid(1); +#endif + return n; +#else // the compile should optimize away this + return path; +#endif + } + + void setLastVisitedDirectory(const QString &dir); + void retranslateWindowTitle(); + void retranslateStrings(); + void emitFilesSelected(const QStringList &files); + + void _q_goHome(); + void _q_pathChanged(const QString &); + void _q_navigateBackward(); + void _q_navigateForward(); + void _q_navigateToParent(); + void _q_createDirectory(); + void _q_showListView(); + void _q_showDetailsView(); + void _q_showContextMenu(const QPoint &position); + void _q_renameCurrent(); + void _q_deleteCurrent(); + void _q_showHidden(); + void _q_showHeader(QAction *); + void _q_updateOkButton(); + void _q_currentChanged(const QModelIndex &index); + void _q_enterDirectory(const QModelIndex &index); + void _q_goToDirectory(const QString &); + void _q_useNameFilter(int index); + void _q_selectionChanged(); + void _q_goToUrl(const QUrl &url); + void _q_autoCompleteFileName(const QString &); + void _q_rowsInserted(const QModelIndex & parent); + void _q_fileRenamed(const QString &path, const QString oldName, const QString newName); + + // layout +#ifndef QT_NO_PROXYMODEL + QAbstractProxyModel *proxyModel; +#endif + + // data + QStringList watching; + QFileSystemModel *model; + +#ifndef QT_NO_FSCOMPLETER + QFSCompleter *completer; +#endif //QT_NO_FSCOMPLETER + + QFileDialog::FileMode fileMode; + QFileDialog::AcceptMode acceptMode; + bool confirmOverwrite; + QString defaultSuffix; + QString setWindowTitle; + + QStringList currentHistory; + int currentHistoryLocation; + + QAction *renameAction; + QAction *deleteAction; + QAction *showHiddenAction; + QAction *newFolderAction; + + bool useDefaultCaption; + bool defaultFileTypes; + bool fileNameLabelExplicitlySat; + QStringList nameFilters; + + // Members for using native dialogs: + bool nativeDialogInUse; + // setVisible_sys returns true if it ends up showing a native + // dialog. Returning false means that a non-native dialog must be + // used instead. + bool canBeNativeDialog(); + bool setVisible_sys(bool visible); + void deleteNativeDialog_sys(); + QDialog::DialogCode dialogResultCode_sys(); + + void setDirectory_sys(const QString &directory); + QString directory_sys() const; + void selectFile_sys(const QString &filename); + QStringList selectedFiles_sys() const; + void setFilter_sys(); + void setNameFilters_sys(const QStringList &filters); + void selectNameFilter_sys(const QString &filter); + QString selectedNameFilter_sys() const; + ////////////////////////////////////////////// + +#if defined(Q_WS_MAC) + void *mDelegate; +#ifndef QT_MAC_USE_COCOA + NavDialogRef mDialog; + bool mDialogStarted; + bool mDialogClosed; + QString mCurrentLocation; + QString mCurrentSelection; + QStringList mCurrentSelectionList; + + struct QtMacFilterName { + QString description; + QString regexp; + QString filter; + }; + struct QtMacNavFilterInfo { + QtMacNavFilterInfo() : currentSelection(-1) {} + int currentSelection; + QList<QtMacFilterName> filters; + } filterInfo; + + static void qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg, NavCBRecPtr p, + NavCallBackUserData data); + static Boolean qt_mac_filedialog_filter_proc(AEDesc *theItem, void *info, void *data, + NavFilterModes); + bool showCarbonNavServicesDialog(); + bool hideCarbonNavServicesDialog(); + void createNavServicesDialog(); +#else + bool showCocoaFilePanel(); + bool hideCocoaFilePanel(); +#endif + void createNSOpenSavePanelDelegate(); + void QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath); + void QNSOpenSavePanelDelegate_panelClosed(bool accepted); + void QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir); + void QNSOpenSavePanelDelegate_filterSelected(int menuIndex); + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); +#endif + + QScopedPointer<Ui_QFileDialog> qFileDialogUi; + + QString acceptLabel; + + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + QByteArray signalToDisconnectOnClose; + + QFileDialog::Options opts; + + ~QFileDialogPrivate(); + +private: + Q_DISABLE_COPY(QFileDialogPrivate) +}; + +class QFileDialogLineEdit : public QLineEdit +{ +public: + QFileDialogLineEdit(QWidget *parent = 0) : QLineEdit(parent), hideOnEsc(false), d_ptr(0){} + void init(QFileDialogPrivate *d_pointer) {d_ptr = d_pointer; } + void keyPressEvent(QKeyEvent *e); + bool hideOnEsc; +private: + QFileDialogPrivate *d_ptr; +}; + +class QFileDialogComboBox : public QComboBox +{ +public: + QFileDialogComboBox(QWidget *parent = 0) : QComboBox(parent), urlModel(0) {} + void init(QFileDialogPrivate *d_pointer); + void showPopup(); + void setHistory(const QStringList &paths); + QStringList history() const { return m_history; } + void paintEvent(QPaintEvent *); + +private: + QUrlModel *urlModel; + QFileDialogPrivate *d_ptr; + QStringList m_history; +}; + +class QFileDialogListView : public QListView +{ +public: + QFileDialogListView(QWidget *parent = 0); + void init(QFileDialogPrivate *d_pointer); + QSize sizeHint() const; +protected: + void keyPressEvent(QKeyEvent *e); +private: + QFileDialogPrivate *d_ptr; +}; + +class QFileDialogTreeView : public QTreeView +{ +public: + QFileDialogTreeView(QWidget *parent); + void init(QFileDialogPrivate *d_pointer); + QSize sizeHint() const; + +protected: + void keyPressEvent(QKeyEvent *e); +private: + QFileDialogPrivate *d_ptr; +}; + +inline QModelIndex QFileDialogPrivate::mapToSource(const QModelIndex &index) const { +#ifdef QT_NO_PROXYMODEL + return index; +#else + return proxyModel ? proxyModel->mapToSource(index) : index; +#endif +} +inline QModelIndex QFileDialogPrivate::mapFromSource(const QModelIndex &index) const { +#ifdef QT_NO_PROXYMODEL + return index; +#else + return proxyModel ? proxyModel->mapFromSource(index) : index; +#endif +} + +inline QString QFileDialogPrivate::rootPath() const { + return model->rootPath(); +} + +#ifndef Q_WS_MAC + // Dummies for platforms that don't use native dialogs: + inline void QFileDialogPrivate::deleteNativeDialog_sys() { qt_guiPlatformPlugin()->fileDialogDelete(q_func()); } + inline bool QFileDialogPrivate::setVisible_sys(bool visible) { return qt_guiPlatformPlugin()->fileDialogSetVisible(q_func(), visible); } + inline QDialog::DialogCode QFileDialogPrivate::dialogResultCode_sys(){ return qt_guiPlatformPlugin()->fileDialogResultCode(q_func()); } + inline void QFileDialogPrivate::setDirectory_sys(const QString &directory) { qt_guiPlatformPlugin()->fileDialogSetDirectory(q_func(), directory); } + inline QString QFileDialogPrivate::directory_sys() const { return qt_guiPlatformPlugin()->fileDialogDirectory(q_func()); } + inline void QFileDialogPrivate::selectFile_sys(const QString &filename) { qt_guiPlatformPlugin()->fileDialogSelectFile(q_func(), filename); } + inline QStringList QFileDialogPrivate::selectedFiles_sys() const { return qt_guiPlatformPlugin()->fileDialogSelectedFiles(q_func()); } + inline void QFileDialogPrivate::setFilter_sys() { qt_guiPlatformPlugin()->fileDialogSetFilter(q_func()); } + inline void QFileDialogPrivate::setNameFilters_sys(const QStringList &filters) { qt_guiPlatformPlugin()->fileDialogSetNameFilters(q_func(), filters); } + inline void QFileDialogPrivate::selectNameFilter_sys(const QString &filter) { qt_guiPlatformPlugin()->fileDialogSelectNameFilter(q_func(), filter); } + inline QString QFileDialogPrivate::selectedNameFilter_sys() const { return qt_guiPlatformPlugin()->fileDialogSelectedNameFilter(q_func()); } +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + +#endif // QFILEDIALOG_P_H diff --git a/src/widgets/dialogs/qfiledialog_symbian.cpp b/src/widgets/dialogs/qfiledialog_symbian.cpp new file mode 100644 index 0000000000..a4a7a228ba --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_symbian.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +#include <private/qfiledialog_p.h> +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) +#include <driveinfo.h> +#include <AknCommonDialogsDynMem.h> +#include <CAknMemorySelectionDialogMultiDrive.h> +#include <MAknFileFilter.h> +#endif +#include "private/qcore_symbian_p.h" + +QT_BEGIN_NAMESPACE + +extern QStringList qt_make_filter_list(const QString &filter); // defined in qfiledialog.cpp +extern QStringList qt_clean_filter_list(const QString &filter); // defined in qfiledialog.cpp + +enum DialogMode { DialogOpen, DialogSave, DialogFolder }; +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) +class CExtensionFilter : public MAknFileFilter +{ +public: + void setFilter(const QString filter) + { + QStringList unparsedFiltersList = qt_make_filter_list(filter); + QStringList filterList; + filterRxList.clear(); + + foreach (QString unparsedFilter, unparsedFiltersList) { + filterList << qt_clean_filter_list(unparsedFilter); + } + foreach (QString currentFilter, filterList) { + QRegExp filterRx(currentFilter, Qt::CaseInsensitive, QRegExp::Wildcard); + filterRxList << filterRx; + } + } + + TBool Accept(const TDesC &/*aDriveAndPath*/, const TEntry &aEntry) const + { + //If no filter for files, all can be accepted + if (filterRxList.isEmpty()) + return ETrue; + + if (aEntry.IsDir()) + return ETrue; + + foreach (QRegExp rx, filterRxList) { + QString fileName = qt_TDesC2QString(aEntry.iName); + if (rx.exactMatch(fileName)) + return ETrue; + } + + return EFalse; + } + +private: + QList<QRegExp> filterRxList; +}; +#endif + +static QString launchSymbianDialog(const QString dialogCaption, const QString startDirectory, + const QString filter, DialogMode dialogMode) +{ + QString selection; +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) + TFileName startFolder; + if (!startDirectory.isEmpty()) { + QString dir = QDir::toNativeSeparators(QFileDialogPrivate::workingDirectory(startDirectory)); + startFolder = qt_QString2TPtrC(dir); + } + TInt types = AknCommonDialogsDynMem::EMemoryTypeMMCExternal| + AknCommonDialogsDynMem::EMemoryTypeInternalMassStorage| + AknCommonDialogsDynMem::EMemoryTypePhone; + + TPtrC titlePtr(qt_QString2TPtrC(dialogCaption)); + TFileName target; + bool select = false; + int tryCount = 2; + while (tryCount--) { + TInt err(KErrNone); + TRAP(err, + if (dialogMode == DialogOpen) { + CExtensionFilter* extensionFilter = new (ELeave) CExtensionFilter; + CleanupStack::PushL(extensionFilter); + extensionFilter->setFilter(filter); + select = AknCommonDialogsDynMem::RunSelectDlgLD(types, target, + startFolder, 0, 0, titlePtr, extensionFilter); + CleanupStack::Pop(extensionFilter); + } else if (dialogMode == DialogSave) { + QString defaultFileName = QFileDialogPrivate::initialSelection(startDirectory); + target = qt_QString2TPtrC(defaultFileName); + select = AknCommonDialogsDynMem::RunSaveDlgLD(types, target, + startFolder, 0, 0, titlePtr); + } else if (dialogMode == DialogFolder) { + select = AknCommonDialogsDynMem::RunFolderSelectDlgLD(types, target, startFolder, + 0, 0, titlePtr, NULL, NULL); + } + ); + + if (err == KErrNone) { + tryCount = 0; + } else { + // Symbian native file dialog doesn't allow accessing files outside C:/Data + // It will always leave in that case, so default into QDir::rootPath() in error cases. + QString dir = QDir::toNativeSeparators(QDir::rootPath()); + startFolder = qt_QString2TPtrC(dir); + } + } + if (select) { + QFileInfo fi(qt_TDesC2QString(target)); + selection = fi.absoluteFilePath(); + } +#endif + return selection; +} + +QString qtSymbianGetOpenFileName(const QString &caption, + const QString &dir, + const QString &filter) +{ + return launchSymbianDialog(caption, dir, filter, DialogOpen); +} + +QStringList qtSymbianGetOpenFileNames(const QString &caption, + const QString &dir, + const QString &filter) +{ + QString fileName; + fileName.append(launchSymbianDialog(caption, dir, filter, DialogOpen)); + QStringList fileList; + fileList << fileName; + + return fileList; +} + +QString qtSymbianGetSaveFileName(const QString &caption, + const QString &dir) +{ + return launchSymbianDialog(caption, dir, QString(), DialogSave); +} + +QString qtSymbianGetExistingDirectory(const QString &caption, + const QString &dir) +{ + QString folderCaption; + if (!caption.isEmpty()) { + folderCaption.append(caption); + } else { + // Title for folder selection dialog is mandatory + folderCaption.append(QFileDialog::tr("Find Directory")); + } + return launchSymbianDialog(folderCaption, dir, QString(), DialogFolder); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qfiledialog_win.cpp b/src/widgets/dialogs/qfiledialog_win.cpp new file mode 100644 index 0000000000..8c3b3cfda7 --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_win.cpp @@ -0,0 +1,839 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +#include <private/qfiledialog_p.h> +#include <qapplication.h> +#include <private/qapplication_p.h> +#include <qt_windows.h> +#include <qglobal.h> +#include <qregexp.h> +#include <qbuffer.h> +#include <qdir.h> +#include <qstringlist.h> +#include <private/qsystemlibrary_p.h> +#include "qfiledialog_win_p.h" +#include "qplatformnativeinterface_qpa.h" + +#ifndef QT_NO_THREAD +# include <private/qmutexpool_p.h> +#endif + +#ifdef Q_WS_WINCE +#include <commdlg.h> +bool qt_priv_ptr_valid = false; +#else +//we have to declare them here because they're not present for all SDK/compilers +static const IID QT_IID_IFileOpenDialog = {0xd57c7288, 0xd4ad, 0x4768, {0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60} }; +static const IID QT_IID_IShellItem = {0x43826d1e, 0xe718, 0x42ee, {0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe} }; +static const CLSID QT_CLSID_FileOpenDialog = {0xdc1c5a9c, 0xe88a, 0x4dde, {0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7} }; +#endif + + +typedef qt_LPITEMIDLIST (WINAPI *PtrSHBrowseForFolder)(qt_BROWSEINFO*); +static PtrSHBrowseForFolder ptrSHBrowseForFolder = 0; +typedef BOOL (WINAPI *PtrSHGetPathFromIDList)(qt_LPITEMIDLIST, LPWSTR); +static PtrSHGetPathFromIDList ptrSHGetPathFromIDList = 0; +typedef HRESULT (WINAPI *PtrSHGetMalloc)(LPMALLOC *); +static PtrSHGetMalloc ptrSHGetMalloc = 0; + + +QT_BEGIN_NAMESPACE + +static void qt_win_resolve_libs() +{ + static bool triedResolve = false; + + if (!triedResolve) { +#ifndef QT_NO_THREAD + // protect initialization + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + // check triedResolve again, since another thread may have already + // done the initialization + if (triedResolve) { + // another thread did initialize the security function pointers, + // so we shouldn't do it again. + return; + } +#endif + + triedResolve = true; +#if !defined(Q_WS_WINCE) + QSystemLibrary lib(L"shell32"); + ptrSHBrowseForFolder = (PtrSHBrowseForFolder)lib.resolve("SHBrowseForFolderW"); + ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)lib.resolve("SHGetPathFromIDListW"); + ptrSHGetMalloc = (PtrSHGetMalloc)lib.resolve("SHGetMalloc"); +#else + // CE stores them in a different lib and does not use unicode version + HINSTANCE handle = LoadLibrary(L"Ceshell"); + ptrSHBrowseForFolder = (PtrSHBrowseForFolder)GetProcAddress(handle, L"SHBrowseForFolder"); + ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)GetProcAddress(handle, L"SHGetPathFromIDList"); + ptrSHGetMalloc = (PtrSHGetMalloc)GetProcAddress(handle, L"SHGetMalloc"); + if (ptrSHBrowseForFolder && ptrSHGetPathFromIDList && ptrSHGetMalloc) + qt_priv_ptr_valid = true; +#endif + } +} + +extern const char* qt_file_dialog_filter_reg_exp; // defined in qfiledialog.cpp +extern QStringList qt_make_filter_list(const QString &filter); + +const int maxNameLen = 1023; +const int maxMultiLen = 65535; + +// Returns the wildcard part of a filter. +static QString qt_win_extract_filter(const QString &rawFilter) +{ + QString result = rawFilter; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + int index = r.indexIn(result); + if (index >= 0) + result = r.cap(2); + QStringList list = result.split(QLatin1Char(' ')); + for(QStringList::iterator it = list.begin(); it < list.end(); ++it) { + if (*it == QLatin1String("*")) { + *it = QLatin1String("*.*"); + break; + } + } + return list.join(QLatin1String(";")); +} + +static QStringList qt_win_make_filters_list(const QString &filter) +{ + QString f(filter); + + if (f.isEmpty()) + f = QFileDialog::tr("All Files (*.*)"); + + return qt_make_filter_list(f); +} + +// Makes a NUL-oriented Windows filter from a Qt filter. +static QString qt_win_filter(const QString &filter, bool hideFiltersDetails) +{ + QStringList filterLst = qt_win_make_filters_list(filter); + QStringList::Iterator it = filterLst.begin(); + QString winfilters; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + for (; it != filterLst.end(); ++it) { + QString subfilter = *it; + if (!subfilter.isEmpty()) { + if (hideFiltersDetails) { + int index = r.indexIn(subfilter); + if (index >= 0) + winfilters += r.cap(1); + } else { + winfilters += subfilter; + } + winfilters += QChar(); + winfilters += qt_win_extract_filter(subfilter); + winfilters += QChar(); + } + } + winfilters += QChar(); + return winfilters; +} + +static QString qt_win_selected_filter(const QString &filter, DWORD idx) +{ + return qt_win_make_filters_list(filter).at((int)idx - 1); +} + +static QString tFilters, tTitle, tInitDir; + +static OPENFILENAME* qt_win_make_OFN(QWidget *parent, + const QString& initialSelection, + const QString& initialDirectory, + const QString& title, + const QString& filters, + QFileDialog::FileMode mode, + QFileDialog::Options options) +{ + if (parent) + parent = parent->window(); + else + parent = QApplication::activeWindow(); + + tInitDir = QDir::toNativeSeparators(initialDirectory); + tFilters = filters; + tTitle = title; + QString initSel = QDir::toNativeSeparators(initialSelection); + if (!initSel.isEmpty()) { + initSel.remove(QLatin1Char('<')); + initSel.remove(QLatin1Char('>')); + initSel.remove(QLatin1Char('\"')); + initSel.remove(QLatin1Char('|')); + } + + int maxLen = mode == QFileDialog::ExistingFiles ? maxMultiLen : maxNameLen; + wchar_t *tInitSel = new wchar_t[maxLen + 1]; + if (initSel.length() > 0 && initSel.length() <= maxLen) + memcpy(tInitSel, initSel.utf16(), (initSel.length()+1)*sizeof(QChar)); + else + tInitSel[0] = 0; + + OPENFILENAME* ofn = new OPENFILENAME; + memset(ofn, 0, sizeof(OPENFILENAME)); + + ofn->lStructSize = sizeof(OPENFILENAME); + ofn->hwndOwner = QApplicationPrivate::getHWNDForWidget(parent); + ofn->lpstrFilter = (wchar_t*)tFilters.utf16(); + ofn->lpstrFile = tInitSel; + ofn->nMaxFile = maxLen; + ofn->lpstrInitialDir = (wchar_t*)tInitDir.utf16(); + ofn->lpstrTitle = (wchar_t*)tTitle.utf16(); + ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST); + if (mode == QFileDialog::ExistingFile || + mode == QFileDialog::ExistingFiles) + ofn->Flags |= (OFN_FILEMUSTEXIST); + if (mode == QFileDialog::ExistingFiles) + ofn->Flags |= (OFN_ALLOWMULTISELECT); + if (!(options & QFileDialog::DontConfirmOverwrite)) + ofn->Flags |= OFN_OVERWRITEPROMPT; + + return ofn; +} + +static void qt_win_clean_up_OFN(OPENFILENAME **ofn) +{ + delete [] (*ofn)->lpstrFile; + delete *ofn; + *ofn = 0; +} + +void qt_win_eatMouseMove() +{ + // after closing a windows dialog with a double click (i.e. open a file) + // the message queue still contains a dubious WM_MOUSEMOVE message where + // the left button is reported to be down (wParam != 0). + // remove all those messages (usually 1) and post the last one with a + // reset button state + + MSG msg = {0, 0, 0, 0, 0, {0, 0} }; + while (PeekMessage(&msg, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) + ; + if (msg.message == WM_MOUSEMOVE) + PostMessage(msg.hwnd, msg.message, 0, msg.lParam); +} + +QString qt_win_get_open_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QString result; + + QString isel = args.selection; + + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + QFileInfo fi(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + if (isel.isEmpty()) + isel = fi.fileName(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + int idx = 0; + if (selectedFilter) { + QStringList filterLst = qt_win_make_filters_list(args.filter); + idx = filterLst.indexOf(*selectedFilter); + } + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::ExistingFile, + args.options); + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetOpenFileName(ofn)) { + result = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + } + qt_win_clean_up_OFN(&ofn); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (result.isEmpty()) + return result; + + fi = result; + *initialDirectory = fi.path(); + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + return fi.absoluteFilePath(); +} + +QString qt_win_get_save_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QString result; + + QString isel = args.selection; + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + QFileInfo fi(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + if (isel.isEmpty()) + isel = fi.fileName(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + int idx = 0; + if (selectedFilter) { + QStringList filterLst = qt_win_make_filters_list(args.filter); + idx = filterLst.indexOf(*selectedFilter); + } + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + // This block is used below for the lpstrDefExt member. + // Note that the current MSDN docs document this member wrong. + // It should rather be documented as "the default extension if no extension was given and if the + // current filter does not have a extension (e.g (*)). If the current filter have an extension, use + // the extension of the current filter" + QString defaultSaveExt; + if (selectedFilter && !selectedFilter->isEmpty()) { + defaultSaveExt = qt_win_extract_filter(*selectedFilter); + // make sure we only have the extension + int firstDot = defaultSaveExt.indexOf(QLatin1Char('.')); + if (firstDot != -1) { + defaultSaveExt.remove(0, firstDot + 1); + } else { + defaultSaveExt.clear(); + } + } + + OPENFILENAME *ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::AnyFile, + args.options); + + ofn->lpstrDefExt = (wchar_t*)defaultSaveExt.utf16(); + + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetSaveFileName(ofn)) { + result = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + } + qt_win_clean_up_OFN(&ofn); + +#if defined(Q_WS_WINCE) + int semIndex = result.indexOf(QLatin1Char(';')); + if (semIndex >= 0) + result = result.left(semIndex); +#endif + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (result.isEmpty()) + return result; + + fi = result; + *initialDirectory = fi.path(); + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + return fi.absoluteFilePath(); +} + + +#ifndef Q_WS_WINCE + +typedef HRESULT (WINAPI *PtrSHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv); +static PtrSHCreateItemFromParsingName pSHCreateItemFromParsingName = 0; + +static bool qt_win_set_IFileDialogOptions(IFileDialog *pfd, + const QString& initialSelection, + const QString& initialDirectory, + const QString& title, + const QStringList& filterLst, + QFileDialog::FileMode mode, + QFileDialog::Options options) +{ + if (!pSHCreateItemFromParsingName) { + // This function is available only in Vista & above. + QSystemLibrary shellLib(QLatin1String("Shell32")); + pSHCreateItemFromParsingName = (PtrSHCreateItemFromParsingName) + shellLib.resolve("SHCreateItemFromParsingName"); + if (!pSHCreateItemFromParsingName) + return false; + } + HRESULT hr; + QString winfilters; + int numFilters = 0; + quint32 currentOffset = 0; + QList<quint32> offsets; + QStringList::ConstIterator it = filterLst.begin(); + // Create the native filter string and save offset to each entry. + for (; it != filterLst.end(); ++it) { + QString subfilter = *it; + if (!subfilter.isEmpty()) { + offsets<<currentOffset; + //Here the COMMON_ITEM_DIALOG API always add the details for the filter (e.g. *.txt) + //so we don't need to handle the flag HideNameFilterDetails. + winfilters += subfilter; // The name of the filter. + winfilters += QChar(); + currentOffset += subfilter.size()+1; + offsets<<currentOffset; + QString spec = qt_win_extract_filter(subfilter); + winfilters += spec; // The actual filter spec. + winfilters += QChar(); + currentOffset += spec.size()+1; + numFilters++; + } + } + // Add the filters to the file dialog. + if (numFilters) { + wchar_t *szData = (wchar_t*)winfilters.utf16(); + qt_COMDLG_FILTERSPEC *filterSpec = new qt_COMDLG_FILTERSPEC[numFilters]; + for(int i = 0; i<numFilters; i++) { + filterSpec[i].pszName = szData+offsets[i*2]; + filterSpec[i].pszSpec = szData+offsets[(i*2)+1]; + } + hr = pfd->SetFileTypes(numFilters, filterSpec); + delete []filterSpec; + } + // Set the starting folder. + tInitDir = QDir::toNativeSeparators(initialDirectory); + if (!tInitDir.isEmpty()) { + IShellItem *psiDefaultFolder; + hr = pSHCreateItemFromParsingName((wchar_t*)tInitDir.utf16(), NULL, QT_IID_IShellItem, + reinterpret_cast<void**>(&psiDefaultFolder)); + + if (SUCCEEDED(hr)) { + hr = pfd->SetFolder(psiDefaultFolder); + psiDefaultFolder->Release(); + } + } + // Set the currently selected file. + QString initSel = QDir::toNativeSeparators(initialSelection); + if (!initSel.isEmpty()) { + initSel.remove(QLatin1Char('<')); + initSel.remove(QLatin1Char('>')); + initSel.remove(QLatin1Char('\"')); + initSel.remove(QLatin1Char('|')); + } + if (!initSel.isEmpty()) { + hr = pfd->SetFileName((wchar_t*)initSel.utf16()); + } + // Set the title for the file dialog. + if (!title.isEmpty()) { + hr = pfd->SetTitle((wchar_t*)title.utf16()); + } + // Set other flags for the dialog. + DWORD newOptions; + hr = pfd->GetOptions(&newOptions); + if (SUCCEEDED(hr)) { + newOptions |= FOS_NOCHANGEDIR; + if (mode == QFileDialog::ExistingFile || + mode == QFileDialog::ExistingFiles) + newOptions |= (FOS_FILEMUSTEXIST | FOS_PATHMUSTEXIST); + if (mode == QFileDialog::ExistingFiles) + newOptions |= FOS_ALLOWMULTISELECT; + if (!(options & QFileDialog::DontConfirmOverwrite)) + newOptions |= FOS_OVERWRITEPROMPT; + hr = pfd->SetOptions(newOptions); + } + return SUCCEEDED(hr); +} + +static QStringList qt_win_CID_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + const QStringList &filterList, + QString *selectedFilter, + int selectedFilterIndex) +{ + QStringList result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + // Multiple selection is allowed only in IFileOpenDialog. + IFileOpenDialog *pfd = 0; + HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, QT_IID_IFileOpenDialog, + reinterpret_cast<void**>(&pfd)); + + if (SUCCEEDED(hr)) { + qt_win_set_IFileDialogOptions(pfd, args.selection, + args.directory, args.caption, + filterList, QFileDialog::ExistingFiles, + args.options); + // Set the currently selected filter (one-based index). + hr = pfd->SetFileTypeIndex(selectedFilterIndex+1); + QWidget *parentWindow = args.parent; + if (parentWindow) + parentWindow = parentWindow->window(); + else + parentWindow = QApplication::activeWindow(); + // Show the file dialog. + hr = pfd->Show(QApplicationPrivate::getHWNDForWidget(parentWindow)); + if (SUCCEEDED(hr)) { + // Retrieve the results. + IShellItemArray *psiaResults; + hr = pfd->GetResults(&psiaResults); + if (SUCCEEDED(hr)) { + DWORD numItems = 0; + psiaResults->GetCount(&numItems); + for (DWORD i = 0; i<numItems; i++) { + IShellItem *psi = 0; + hr = psiaResults->GetItemAt(i, &psi); + if (SUCCEEDED(hr)) { + // Retrieve the file name from shell item. + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + QString fileName = QString::fromWCharArray(pszPath); + result.append(fileName); + CoTaskMemFree(pszPath); + } + psi->Release(); // Free the current item. + } + } + psiaResults->Release(); // Free the array of items. + } + } + } + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) { + // Retrieve the current folder name. + IShellItem *psi = 0; + hr = pfd->GetFolder(&psi); + if (SUCCEEDED(hr)) { + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + *initialDirectory = QString::fromWCharArray(pszPath); + CoTaskMemFree(pszPath); + } + psi->Release(); + } + // Retrieve the currently selected filter. + if (selectedFilter) { + quint32 filetype = 0; + hr = pfd->GetFileTypeIndex(&filetype); + if (SUCCEEDED(hr) && filetype && filetype <= (quint32)filterList.length()) { + // This is a one-based index, not zero-based. + *selectedFilter = filterList[filetype-1]; + } + } + } + if (pfd) + pfd->Release(); + return result; +} + +QString qt_win_CID_get_existing_directory(const QFileDialogArgs &args) +{ + QString result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + IFileOpenDialog *pfd = 0; + HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, + QT_IID_IFileOpenDialog, reinterpret_cast<void**>(&pfd)); + + if (SUCCEEDED(hr)) { + qt_win_set_IFileDialogOptions(pfd, args.selection, + args.directory, args.caption, + QStringList(), QFileDialog::ExistingFiles, + args.options); + + // Set the FOS_PICKFOLDERS flag + DWORD newOptions; + hr = pfd->GetOptions(&newOptions); + newOptions |= (FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + if (SUCCEEDED(hr) && SUCCEEDED((hr = pfd->SetOptions(newOptions)))) { + QWidget *parentWindow = args.parent; + if (parentWindow) + parentWindow = parentWindow->window(); + else + parentWindow = QApplication::activeWindow(); + + // Show the file dialog. + hr = pfd->Show(QApplicationPrivate::getHWNDForWidget(parentWindow)); + if (SUCCEEDED(hr)) { + // Retrieve the result + IShellItem *psi = 0; + hr = pfd->GetResult(&psi); + if (SUCCEEDED(hr)) { + // Retrieve the file name from shell item. + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + result = QString::fromWCharArray(pszPath); + CoTaskMemFree(pszPath); + } + psi->Release(); // Free the current item. + } + } + } + } + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (pfd) + pfd->Release(); + return result; +} + +#endif + +QStringList qt_win_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QFileInfo fi; + QDir dir; + + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + fi = QFileInfo(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + QStringList filterLst = qt_win_make_filters_list(args.filter); + int idx = 0; + if (selectedFilter) { + idx = filterLst.indexOf(*selectedFilter); + } + // Windows Vista (& above) allows users to search from file dialogs. If user selects + // multiple files belonging to different folders from these search results, the + // GetOpenFileName() will return only one folder name for all the files. To retrieve + // the correct path for all selected files, we have to use Common Item Dialog interfaces. +#ifndef Q_WS_WINCE + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + return qt_win_CID_get_open_file_names(args, initialDirectory, filterLst, selectedFilter, idx); +#endif + + QStringList result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::ExistingFiles, + args.options); + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetOpenFileName(ofn)) { + QString fileOrDir = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + int offset = fileOrDir.length() + 1; + if (ofn->lpstrFile[offset] == 0) { + // Only one file selected; has full path + fi.setFile(fileOrDir); + QString res = fi.absoluteFilePath(); + if (!res.isEmpty()) + result.append(res); + } + else { + // Several files selected; first string is path + dir.setPath(fileOrDir); + QString f; + while(!(f = QString::fromWCharArray(ofn->lpstrFile + offset)).isEmpty()) { + fi.setFile(dir, f); + QString res = fi.absoluteFilePath(); + if (!res.isEmpty()) + result.append(res); + offset += f.length() + 1; + } + } + } + qt_win_clean_up_OFN(&ofn); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) { + *initialDirectory = fi.path(); // only save the path if there is a result + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + } + return result; +} + +// MFC Directory Dialog. Contrib: Steve Williams (minor parts from Scott Powers) + +static int __stdcall winGetExistDirCallbackProc(HWND hwnd, + UINT uMsg, + LPARAM lParam, + LPARAM lpData) +{ + if (uMsg == BFFM_INITIALIZED && lpData != 0) { + QString *initDir = (QString *)(lpData); + if (!initDir->isEmpty()) { + SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initDir->utf16())); + } + } else if (uMsg == BFFM_SELCHANGED) { + qt_win_resolve_libs(); + if (ptrSHGetPathFromIDList) { + wchar_t path[MAX_PATH]; + ptrSHGetPathFromIDList(qt_LPITEMIDLIST(lParam), path); + QString tmpStr = QString::fromWCharArray(path); + if (!tmpStr.isEmpty()) + SendMessage(hwnd, BFFM_ENABLEOK, 1, 1); + else + SendMessage(hwnd, BFFM_ENABLEOK, 0, 0); + SendMessage(hwnd, BFFM_SETSTATUSTEXT, 1, LPARAM(path)); + } + } + return 0; +} + +QString qt_win_get_existing_directory(const QFileDialogArgs &args) +{ +#ifndef Q_WS_WINCE + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + return qt_win_CID_get_existing_directory(args); +#endif + + QString currentDir = QDir::currentPath(); + QString result; + QWidget *parent = args.parent; + if (parent) + parent = parent->window(); + else + parent = QApplication::activeWindow(); + if (parent) + parent->createWinId(); + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + QString initDir = QDir::toNativeSeparators(args.directory); + wchar_t path[MAX_PATH]; + wchar_t initPath[MAX_PATH]; + initPath[0] = 0; + path[0] = 0; + tTitle = args.caption; + + qt_BROWSEINFO bi; + + Q_ASSERT(!parent ||parent->testAttribute(Qt::WA_WState_Created)); + bi.hwndOwner = QApplicationPrivate::getHWNDForWidget(parent); + bi.pidlRoot = NULL; + //### This does not seem to be respected? - the dialog always displays "Browse for folder" + bi.lpszTitle = (wchar_t*)tTitle.utf16(); + bi.pszDisplayName = initPath; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE; + bi.lpfn = winGetExistDirCallbackProc; + bi.lParam = LPARAM(&initDir); + + qt_win_resolve_libs(); + if (ptrSHBrowseForFolder) { + qt_LPITEMIDLIST pItemIDList = ptrSHBrowseForFolder(&bi); + if (pItemIDList) { + ptrSHGetPathFromIDList(pItemIDList, path); + IMalloc *pMalloc; + if (ptrSHGetMalloc(&pMalloc) == NOERROR) { + pMalloc->Free(pItemIDList); + pMalloc->Release(); + result = QString::fromWCharArray(path); + } + } + } + tTitle = QString(); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) + result.replace(QLatin1Char('\\'), QLatin1Char('/')); + return result; +} + + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qfiledialog_win_p.h b/src/widgets/dialogs/qfiledialog_win_p.h new file mode 100644 index 0000000000..1ff29d2e26 --- /dev/null +++ b/src/widgets/dialogs/qfiledialog_win_p.h @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <objbase.h> +#ifndef QFILEDIAG_WIN_P_H +#define QFILEDIAG_WIN_P_H + +//these are the interface declarations needed for the file dialog on Vista and up + +//At some point we can hope that all compilers/sdk will support that interface +//and we won't have to declare it ourselves + +//declarations +#define FOS_OVERWRITEPROMPT 0x2 +#define FOS_STRICTFILETYPES 0x4 +#define FOS_NOCHANGEDIR 0x8 +#define FOS_PICKFOLDERS 0x20 +#define FOS_FORCEFILESYSTEM 0x40 +#define FOS_ALLNONSTORAGEITEMS 0x80 +#define FOS_NOVALIDATE 0x100 +#define FOS_ALLOWMULTISELECT 0x200 +#define FOS_PATHMUSTEXIST 0x800 +#define FOS_FILEMUSTEXIST 0x1000 +#define FOS_CREATEPROMPT 0x2000 +#define FOS_SHAREAWARE 0x4000 +#define FOS_NOREADONLYRETURN 0x8000 +#define FOS_NOTESTFILECREATE 0x10000 +#define FOS_HIDEMRUPLACES 0x20000 +#define FOS_HIDEPINNEDPLACES 0x40000 +#define FOS_NODEREFERENCELINKS 0x100000 +#define FOS_DONTADDTORECENT 0x2000000 +#define FOS_FORCESHOWHIDDEN 0x10000000 +#define FOS_DEFAULTNOMINIMODE 0x20000000 +#define FOS_FORCEPREVIEWPANEON 0x40000000 + +typedef int GETPROPERTYSTOREFLAGS; +#define GPS_DEFAULT 0x00000000 +#define GPS_HANDLERPROPERTIESONLY 0x00000001 +#define GPS_READWRITE 0x00000002 +#define GPS_TEMPORARY 0x00000004 +#define GPS_FASTPROPERTIESONLY 0x00000008 +#define GPS_OPENSLOWITEM 0x00000010 +#define GPS_DELAYCREATION 0x00000020 +#define GPS_BESTEFFORT 0x00000040 +#define GPS_MASK_VALID 0x0000007F + +typedef int (QT_WIN_CALLBACK* BFFCALLBACK)(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData); +// message from browser +#define BFFM_INITIALIZED 1 +#define BFFM_SELCHANGED 2 +#define BFFM_ENABLEOK (WM_USER + 101) +#define BFFM_SETSELECTION (WM_USER + 103) +#define BFFM_SETSTATUSTEXT (WM_USER + 104) + +// Browsing for directory. +#define BIF_RETURNONLYFSDIRS 0x0001 +#define BIF_DONTGOBELOWDOMAIN 0x0002 +#define BIF_STATUSTEXT 0x0004 +#define BIF_RETURNFSANCESTORS 0x0008 +#define BIF_EDITBOX 0x0010 +#define BIF_VALIDATE 0x0020 +#define BIF_NEWDIALOGSTYLE 0x0040 +#define BIF_BROWSEINCLUDEURLS 0x0080 +#define BIF_UAHINT 0x0100 +#define BIF_NONEWFOLDERBUTTON 0x0200 +#define BIF_NOTRANSLATETARGETS 0x0400 +#define BIF_BROWSEFORCOMPUTER 0x1000 +#define BIF_BROWSEFORPRINTER 0x2000 +#define BIF_BROWSEINCLUDEFILES 0x4000 +#define BIF_SHAREABLE 0x8000 + +//the enums +typedef enum { + SIATTRIBFLAGS_AND = 0x1, + SIATTRIBFLAGS_OR = 0x2, + SIATTRIBFLAGS_APPCOMPAT = 0x3, + SIATTRIBFLAGS_MASK = 0x3 +} SIATTRIBFLAGS; +typedef enum { + SIGDN_NORMALDISPLAY = 0x00000000, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001c001, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_URL = 0x80068000 +} SIGDN; +typedef enum { + FDAP_BOTTOM = 0x00000000, + FDAP_TOP = 0x00000001 +} FDAP; +typedef enum { + FDESVR_DEFAULT = 0x00000000, + FDESVR_ACCEPT = 0x00000001, + FDESVR_REFUSE = 0x00000002 +} FDE_SHAREVIOLATION_RESPONSE; +typedef FDE_SHAREVIOLATION_RESPONSE FDE_OVERWRITE_RESPONSE; + +//the structs +typedef struct { + LPCWSTR pszName; + LPCWSTR pszSpec; +} qt_COMDLG_FILTERSPEC; +typedef struct { + GUID fmtid; + DWORD pid; +} qt_PROPERTYKEY; + +typedef struct { + USHORT cb; + BYTE abID[1]; +} qt_SHITEMID, *qt_LPSHITEMID; +typedef struct { + qt_SHITEMID mkid; +} qt_ITEMIDLIST, *qt_LPITEMIDLIST; +typedef const qt_ITEMIDLIST *qt_LPCITEMIDLIST; +typedef struct { + HWND hwndOwner; + qt_LPCITEMIDLIST pidlRoot; + LPWSTR pszDisplayName; + LPCWSTR lpszTitle; + UINT ulFlags; + BFFCALLBACK lpfn; + LPARAM lParam; + int iImage; +} qt_BROWSEINFO; + +DECLARE_INTERFACE(IFileDialogEvents); +DECLARE_INTERFACE_(IShellItem, IUnknown) +{ + STDMETHOD(BindToHandler)(THIS_ IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppv) PURE; + STDMETHOD(GetParent)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(GetDisplayName)(THIS_ SIGDN sigdnName, LPWSTR *ppszName) PURE; + STDMETHOD(GetAttributes)(THIS_ ULONG sfgaoMask, ULONG *psfgaoAttribs) PURE; + STDMETHOD(Compare)(THIS_ IShellItem *psi, DWORD hint, int *piOrder) PURE; +}; +DECLARE_INTERFACE_(IShellItemFilter, IUnknown) +{ + STDMETHOD(IncludeItem)(THIS_ IShellItem *psi) PURE; + STDMETHOD(GetEnumFlagsForItem)(THIS_ IShellItem *psi, DWORD *pgrfFlags) PURE; +}; +DECLARE_INTERFACE_(IEnumShellItems, IUnknown) +{ + STDMETHOD(Next)(THIS_ ULONG celt, IShellItem **rgelt, ULONG *pceltFetched) PURE; + STDMETHOD(Skip)(THIS_ ULONG celt) PURE; + STDMETHOD(Reset)(THIS_) PURE; + STDMETHOD(Clone)(THIS_ IEnumShellItems **ppenum) PURE; +}; +DECLARE_INTERFACE_(IShellItemArray, IUnknown) +{ + STDMETHOD(BindToHandler)(THIS_ IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppvOut) PURE; + STDMETHOD(GetPropertyStore)(THIS_ GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv) PURE; + STDMETHOD(GetPropertyDescriptionList)(THIS_ const qt_PROPERTYKEY *keyType, REFIID riid, void **ppv) PURE; + STDMETHOD(GetAttributes)(THIS_ SIATTRIBFLAGS dwAttribFlags, ULONG sfgaoMask, ULONG *psfgaoAttribs) PURE; + STDMETHOD(GetCount)(THIS_ DWORD *pdwNumItems) PURE; + STDMETHOD(GetItemAt)(THIS_ DWORD dwIndex, IShellItem **ppsi) PURE; + STDMETHOD(EnumItems)(THIS_ IEnumShellItems **ppenumShellItems) PURE; +}; +DECLARE_INTERFACE_(IModalWindow, IUnknown) +{ + STDMETHOD(Show)(THIS_ HWND hwndParent) PURE; +}; +DECLARE_INTERFACE_(IFileDialog, IModalWindow) +{ + STDMETHOD(SetFileTypes)(THIS_ UINT cFileTypes, const qt_COMDLG_FILTERSPEC *rgFilterSpec) PURE; + STDMETHOD(SetFileTypeIndex)(THIS_ UINT iFileType) PURE; + STDMETHOD(GetFileTypeIndex)(THIS_ UINT *piFileType) PURE; + STDMETHOD(Advise)(THIS_ IFileDialogEvents *pfde, DWORD *pdwCookie) PURE; + STDMETHOD(Unadvise)(THIS_ DWORD dwCookie) PURE; + STDMETHOD(SetOptions)(THIS_ DWORD fos) PURE; + STDMETHOD(GetOptions)(THIS_ DWORD *pfos) PURE; + STDMETHOD(SetDefaultFolder)(THIS_ IShellItem *psi) PURE; + STDMETHOD(SetFolder)(THIS_ IShellItem *psi) PURE; + STDMETHOD(GetFolder)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(GetCurrentSelection)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(SetFileName)(THIS_ LPCWSTR pszName) PURE; + STDMETHOD(GetFileName)(THIS_ LPWSTR *pszName) PURE; + STDMETHOD(SetTitle)(THIS_ LPCWSTR pszTitle) PURE; + STDMETHOD(SetOkButtonLabel)(THIS_ LPCWSTR pszText) PURE; + STDMETHOD(SetFileNameLabel)(THIS_ LPCWSTR pszLabel) PURE; + STDMETHOD(GetResult)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(AddPlace)(THIS_ IShellItem *psi, FDAP fdap) PURE; + STDMETHOD(SetDefaultExtension)(THIS_ LPCWSTR pszDefaultExtension) PURE; + STDMETHOD(Close)(THIS_ HRESULT hr) PURE; + STDMETHOD(SetClientGuid)(THIS_ REFGUID guid) PURE; + STDMETHOD(ClearClientData)(THIS_) PURE; + STDMETHOD(SetFilter)(THIS_ IShellItemFilter *pFilter) PURE; +}; +DECLARE_INTERFACE_(IFileDialogEvents, IUnknown) +{ + STDMETHOD(OnFileOk)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnFolderChanging)(THIS_ IFileDialog *pfd, IShellItem *psiFolder) PURE; + STDMETHOD(OnFolderChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnSelectionChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnShareViolation)(THIS_ IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse) PURE; + STDMETHOD(OnTypeChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnOverwrite)(THIS_ IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse) PURE; +}; +DECLARE_INTERFACE_(IFileOpenDialog, IFileDialog) +{ + STDMETHOD(GetResults)(THIS_ IShellItemArray **ppenum) PURE; + STDMETHOD(GetSelectedItems)(THIS_ IShellItemArray **ppsai) PURE; +}; +#endif
\ No newline at end of file diff --git a/src/widgets/dialogs/qfileinfogatherer.cpp b/src/widgets/dialogs/qfileinfogatherer.cpp new file mode 100644 index 0000000000..315b93131f --- /dev/null +++ b/src/widgets/dialogs/qfileinfogatherer.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfileinfogatherer_p.h" +#include <qdebug.h> +#include <qfsfileengine.h> +#include <qdiriterator.h> +#ifndef Q_OS_WIN +# include <unistd.h> +# include <sys/types.h> +#endif +#if defined(Q_OS_VXWORKS) +# include "qplatformdefs.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_FILESYSTEMMODEL + +#ifdef QT_BUILD_INTERNAL +static bool fetchedRoot = false; +Q_AUTOTEST_EXPORT void qt_test_resetFetchedRoot() +{ + fetchedRoot = false; +} + +Q_AUTOTEST_EXPORT bool qt_test_isFetchedRoot() +{ + return fetchedRoot; +} +#endif + +/*! + Creates thread +*/ +QFileInfoGatherer::QFileInfoGatherer(QObject *parent) + : QThread(parent), abort(false), +#ifndef QT_NO_FILESYSTEMWATCHER + watcher(0), +#endif + m_resolveSymlinks(false), m_iconProvider(&defaultProvider) +{ +#ifdef Q_OS_WIN + m_resolveSymlinks = true; +#elif !defined(Q_OS_INTEGRITY) + userId = getuid(); + groupId = getgid(); +#endif +#ifndef QT_NO_FILESYSTEMWATCHER + watcher = new QFileSystemWatcher(this); + connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(list(QString))); + connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(updateFile(QString))); +#endif + start(LowPriority); +} + +/*! + Destroys thread +*/ +QFileInfoGatherer::~QFileInfoGatherer() +{ + QMutexLocker locker(&mutex); + abort = true; + condition.wakeOne(); + locker.unlock(); + wait(); +} + +void QFileInfoGatherer::setResolveSymlinks(bool enable) +{ + Q_UNUSED(enable); +#ifdef Q_OS_WIN + QMutexLocker locker(&mutex); + m_resolveSymlinks = enable; +#endif +} + +bool QFileInfoGatherer::resolveSymlinks() const +{ + return m_resolveSymlinks; +} + +void QFileInfoGatherer::setIconProvider(QFileIconProvider *provider) +{ + QMutexLocker locker(&mutex); + m_iconProvider = provider; +} + +QFileIconProvider *QFileInfoGatherer::iconProvider() const +{ + return m_iconProvider; +} + +/*! + Fetch extended information for all \a files in \a path + + \sa updateFile(), update(), resolvedName() +*/ +void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStringList &files) +{ + QMutexLocker locker(&mutex); + // See if we already have this dir/file in our que + int loc = this->path.lastIndexOf(path); + while (loc > 0) { + if (this->files.at(loc) == files) { + return; + } + loc = this->path.lastIndexOf(path, loc - 1); + } + this->path.push(path); + this->files.push(files); + condition.wakeAll(); +} + +/*! + Fetch extended information for all \a filePath + + \sa fetchExtendedInformation() +*/ +void QFileInfoGatherer::updateFile(const QString &filePath) +{ + QString dir = filePath.mid(0, filePath.lastIndexOf(QDir::separator())); + QString fileName = filePath.mid(dir.length() + 1); + fetchExtendedInformation(dir, QStringList(fileName)); +} + +/* + List all files in \a directoryPath + + \sa listed() +*/ +void QFileInfoGatherer::clear() +{ +#ifndef QT_NO_FILESYSTEMWATCHER + QMutexLocker locker(&mutex); + watcher->removePaths(watcher->files()); + watcher->removePaths(watcher->directories()); +#endif +} + +/* + Remove a \a path from the watcher + + \sa listed() +*/ +void QFileInfoGatherer::removePath(const QString &path) +{ +#ifndef QT_NO_FILESYSTEMWATCHER + QMutexLocker locker(&mutex); + watcher->removePath(path); +#endif +} + +/* + List all files in \a directoryPath + + \sa listed() +*/ +void QFileInfoGatherer::list(const QString &directoryPath) +{ + fetchExtendedInformation(directoryPath, QStringList()); +} + +/* + Until aborted wait to fetch a directory or files +*/ +void QFileInfoGatherer::run() +{ + forever { + bool updateFiles = false; + QMutexLocker locker(&mutex); + if (abort) { + return; + } + if (this->path.isEmpty()) + condition.wait(&mutex); + QString path; + QStringList list; + if (!this->path.isEmpty()) { + path = this->path.first(); + list = this->files.first(); + this->path.pop_front(); + this->files.pop_front(); + updateFiles = true; + } + locker.unlock(); + if (updateFiles) + getFileInfos(path, list); + } +} + +QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const +{ + QExtendedInformation info(fileInfo); + info.icon = m_iconProvider->icon(fileInfo); + info.displayType = m_iconProvider->type(fileInfo); +#ifndef QT_NO_FILESYSTEMWATCHER + // ### Not ready to listen all modifications + #if 0 + // Enable the next two commented out lines to get updates when the file sizes change... + if (!fileInfo.exists() && !fileInfo.isSymLink()) { + info.size = -1; + //watcher->removePath(fileInfo.absoluteFilePath()); + } else { + if (!fileInfo.absoluteFilePath().isEmpty() && fileInfo.exists() && fileInfo.isReadable() + && !watcher->files().contains(fileInfo.absoluteFilePath())) { + //watcher->addPath(fileInfo.absoluteFilePath()); + } + } + #endif +#endif + + if (fileInfo.isSymLink() && m_resolveSymlinks) { + QFileInfo resolvedInfo(fileInfo.symLinkTarget()); + resolvedInfo = resolvedInfo.canonicalFilePath(); + if (resolvedInfo.exists()) { + emit nameResolved(fileInfo.filePath(), resolvedInfo.fileName()); + } + } + return info; +} + +QString QFileInfoGatherer::translateDriveName(const QFileInfo &drive) const +{ + QString driveName = drive.absoluteFilePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (driveName.startsWith(QLatin1Char('/'))) // UNC host + return drive.fileName(); +#endif +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + if (driveName.endsWith(QLatin1Char('/'))) + driveName.chop(1); +#endif + return driveName; +} + +/* + Get specific file info's, batch the files so update when we have 100 + items and every 200ms after that + */ +void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &files) +{ +#ifndef QT_NO_FILESYSTEMWATCHER + if (files.isEmpty() + && !watcher->directories().contains(path) + && !path.isEmpty() + && !path.startsWith(QLatin1String("//")) /*don't watch UNC path*/) { + watcher->addPath(path); + } +#endif + + // List drives + if (path.isEmpty()) { +#ifdef QT_BUILD_INTERNAL + fetchedRoot = true; +#endif + QFileInfoList infoList; + if (files.isEmpty()) { + infoList = QDir::drives(); + } else { + for (int i = 0; i < files.count(); ++i) + infoList << QFileInfo(files.at(i)); + } + for (int i = infoList.count() - 1; i >= 0; --i) { + QString driveName = translateDriveName(infoList.at(i)); + QList<QPair<QString,QFileInfo> > updatedFiles; + updatedFiles.append(QPair<QString,QFileInfo>(driveName, infoList.at(i))); + emit updates(path, updatedFiles); + } + return; + } + + QElapsedTimer base; + base.start(); + QFileInfo fileInfo; + bool firstTime = true; + QList<QPair<QString, QFileInfo> > updatedFiles; + QStringList filesToCheck = files; + + QString itPath = QDir::fromNativeSeparators(files.isEmpty() ? path : QLatin1String("")); + QDirIterator dirIt(itPath, QDir::AllEntries | QDir::System | QDir::Hidden); + QStringList allFiles; + while(!abort && dirIt.hasNext()) { + dirIt.next(); + fileInfo = dirIt.fileInfo(); + allFiles.append(fileInfo.fileName()); + fetch(fileInfo, base, firstTime, updatedFiles, path); + } + if (!allFiles.isEmpty()) + emit newListOfFiles(path, allFiles); + + QStringList::const_iterator filesIt = filesToCheck.constBegin(); + while(!abort && filesIt != filesToCheck.constEnd()) { + fileInfo.setFile(path + QDir::separator() + *filesIt); + ++filesIt; + fetch(fileInfo, base, firstTime, updatedFiles, path); + } + if (!updatedFiles.isEmpty()) + emit updates(path, updatedFiles); + emit directoryLoaded(path); +} + +void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime, QList<QPair<QString, QFileInfo> > &updatedFiles, const QString &path) { + updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo)); + QElapsedTimer current; + current.start(); + if ((firstTime && updatedFiles.count() > 100) || base.msecsTo(current) > 1000) { + emit updates(path, updatedFiles); + updatedFiles.clear(); + base = current; + firstTime = false; + } +} + +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE diff --git a/src/widgets/dialogs/qfileinfogatherer_p.h b/src/widgets/dialogs/qfileinfogatherer_p.h new file mode 100644 index 0000000000..98217c1dc8 --- /dev/null +++ b/src/widgets/dialogs/qfileinfogatherer_p.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEINFOGATHERER_H +#define QFILEINFOGATHERER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qthread.h> +#include <qmutex.h> +#include <qwaitcondition.h> +#include <qfilesystemwatcher.h> +#include <qfileiconprovider.h> +#include <qfsfileengine.h> +#include <qpair.h> +#include <qstack.h> +#include <qdatetime.h> +#include <qdir.h> +#include <qelapsedtimer.h> + +QT_BEGIN_NAMESPACE + +class QExtendedInformation { +public: + enum Type { Dir, File, System }; + + QExtendedInformation() {} + QExtendedInformation(const QFileInfo &info) : mFileInfo(info) {} + + inline bool isDir() { return type() == Dir; } + inline bool isFile() { return type() == File; } + inline bool isSystem() { return type() == System; } + + bool operator ==(const QExtendedInformation &fileInfo) const { + return mFileInfo == fileInfo.mFileInfo + && displayType == fileInfo.displayType + && permissions() == fileInfo.permissions(); + } + +#ifndef QT_NO_FSFILEENGINE + bool isCaseSensitive() const { + QFSFileEngine fe(mFileInfo.absoluteFilePath()); + return fe.caseSensitive(); + } +#endif + + QFile::Permissions permissions() const { + return mFileInfo.permissions(); + } + + Type type() const { + if (mFileInfo.isDir()) { + return QExtendedInformation::Dir; + } + if (mFileInfo.isFile()) { + return QExtendedInformation::File; + } + if (!mFileInfo.exists() && mFileInfo.isSymLink()) { + return QExtendedInformation::System; + } + return QExtendedInformation::System; + } + + bool isSymLink() const { + return mFileInfo.isSymLink(); + } + + bool isHidden() const { + return mFileInfo.isHidden(); + } + + QFileInfo fileInfo() const { + return mFileInfo; + } + + QDateTime lastModified() const { + return mFileInfo.lastModified(); + } + + qint64 size() const { + qint64 size = -1; + if (type() == QExtendedInformation::Dir) + size = 0; + if (type() == QExtendedInformation::File) + size = mFileInfo.size(); + if (!mFileInfo.exists() && !mFileInfo.isSymLink()) + size = -1; + return size; + } + + QString displayType; + QIcon icon; + +private : + QFileInfo mFileInfo; +}; + +class QFileIconProvider; + +#ifndef QT_NO_FILESYSTEMMODEL + +class Q_AUTOTEST_EXPORT QFileInfoGatherer : public QThread +{ +Q_OBJECT + +Q_SIGNALS: + void updates(const QString &directory, const QList<QPair<QString, QFileInfo> > &updates); + void newListOfFiles(const QString &directory, const QStringList &listOfFiles) const; + void nameResolved(const QString &fileName, const QString &resolvedName) const; + void directoryLoaded(const QString &path); + +public: + QFileInfoGatherer(QObject *parent = 0); + ~QFileInfoGatherer(); + + void clear(); + void removePath(const QString &path); + QExtendedInformation getInfo(const QFileInfo &info) const; + +public Q_SLOTS: + void list(const QString &directoryPath); + void fetchExtendedInformation(const QString &path, const QStringList &files); + void updateFile(const QString &path); + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + +protected: + void run(); + void getFileInfos(const QString &path, const QStringList &files); + +private: + void fetch(const QFileInfo &info, QElapsedTimer &base, bool &firstTime, QList<QPair<QString, QFileInfo> > &updatedFiles, const QString &path); + QString translateDriveName(const QFileInfo &drive) const; + + QMutex mutex; + QWaitCondition condition; + volatile bool abort; + + QStack<QString> path; + QStack<QStringList> files; + +#ifndef QT_NO_FILESYSTEMWATCHER + QFileSystemWatcher *watcher; +#endif + bool m_resolveSymlinks; + QFileIconProvider *m_iconProvider; + QFileIconProvider defaultProvider; +#ifndef Q_OS_WIN + uint userId; + uint groupId; +#endif +}; +#endif // QT_NO_FILESYSTEMMODEL + + +QT_END_NAMESPACE +#endif // QFILEINFOGATHERER_H + diff --git a/src/widgets/dialogs/qfilesystemmodel.cpp b/src/widgets/dialogs/qfilesystemmodel.cpp new file mode 100644 index 0000000000..10d627c594 --- /dev/null +++ b/src/widgets/dialogs/qfilesystemmodel.cpp @@ -0,0 +1,2029 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemmodel_p.h" +#include "qfilesystemmodel.h" +#include <qlocale.h> +#include <qmime.h> +#include <qurl.h> +#include <qdebug.h> +#include <qmessagebox.h> +#include <qapplication.h> + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#endif +#ifdef Q_OS_WIN32 +#include <QtCore/QVarLengthArray> +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_FILESYSTEMMODEL + +/*! + \enum QFileSystemModel::Roles + \value FileIconRole + \value FilePathRole + \value FileNameRole + \value FilePermissions +*/ + +/*! + \class QFileSystemModel + \since 4.4 + + \brief The QFileSystemModel class provides a data model for the local filesystem. + + \ingroup model-view + + This class provides access to the local filesystem, providing functions + for renaming and removing files and directories, and for creating new + directories. In the simplest case, it can be used with a suitable display + widget as part of a browser or filter. + + QFileSystemModel can be accessed using the standard interface provided by + QAbstractItemModel, but it also provides some convenience functions that are + specific to a directory model. + The fileInfo(), isDir(), name(), and path() functions provide information + about the underlying files and directories related to items in the model. + Directories can be created and removed using mkdir(), rmdir(). + + \note QFileSystemModel requires an instance of a GUI application. + + \section1 Example Usage + + A directory model that displays the contents of a default directory + is usually constructed with a parent object: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 2 + + A tree view can be used to display the contents of the model + + \snippet doc/src/snippets/shareddirmodel/main.cpp 4 + + and the contents of a particular directory can be displayed by + setting the tree view's root index: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 7 + + The view's root index can be used to control how much of a + hierarchical model is displayed. QDirModel provides a convenience + function that returns a suitable model index for a path to a + directory within the model. + + \section1 Caching and Performance + + QFileSystemModel will not fetch any files or directories until setRootPath() + is called. This will prevent any unnecessary querying on the file system + until that point such as listing the drives on Windows. + + Unlike QDirModel, QFileSystemModel uses a separate thread to populate + itself so it will not cause the main thread to hang as the file system + is being queried. Calls to rowCount() will return 0 until the model + populates a directory. + + QFileSystemModel keeps a cache with file information. The cache is + automatically kept up to date using the QFileSystemWatcher. + + \sa {Model Classes} +*/ + +/*! + \fn bool QFileSystemModel::rmdir(const QModelIndex &index) const + + Removes the directory corresponding to the model item \a index in the + file system model and \bold{deletes the corresponding directory from the + file system}, returning true if successful. If the directory cannot be + removed, false is returned. + + \warning This function deletes directories from the file system; it does + \bold{not} move them to a location where they can be recovered. + + \sa remove() +*/ + +/*! + \fn QIcon QFileSystemModel::fileName(const QModelIndex &index) const + + Returns the file name for the item stored in the model under the given + \a index. +*/ + +/*! + \fn QIcon QFileSystemModel::fileIcon(const QModelIndex &index) const + + Returns the icon for the item stored in the model under the given + \a index. +*/ + +/*! + \fn QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const + + Returns the QFileInfo for the item stored in the model under the given + \a index. +*/ + +/*! + \fn void QFileSystemModel::rootPathChanged(const QString &newPath); + + This signal is emitted whenever the root path has been changed to a \a newPath. +*/ + +/*! + \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName) + + This signal is emitted whenever a file with the \a oldName is successfully + renamed to \a newName. The file is located in in the directory \a path. +*/ + +/*! + \since 4.7 + \fn void QFileSystemModel::directoryLoaded(const QString &path) + + This signal is emitted when the gatherer thread has finished to load the \a path. + +*/ + +/*! + \fn bool QFileSystemModel::remove(const QModelIndex &index) const + + Removes the model item \a index from the file system model and \bold{deletes the + corresponding file from the file system}, returning true if successful. If the + item cannot be removed, false is returned. + + \warning This function deletes files from the file system; it does \bold{not} + move them to a location where they can be recovered. + + \sa rmdir() +*/ + +bool QFileSystemModel::remove(const QModelIndex &aindex) const +{ + //### TODO optim + QString path = filePath(aindex); + QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func()); + d->fileInfoGatherer.removePath(path); + QDirIterator it(path, + QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + QStringList children; + while (it.hasNext()) + children.prepend(it.next()); + children.append(path); + + bool error = false; + for (int i = 0; i < children.count(); ++i) { + QFileInfo info(children.at(i)); + QModelIndex modelIndex = index(children.at(i)); + if (info.isDir()) { + QDir dir; + if (children.at(i) != path) + error |= remove(modelIndex); + error |= rmdir(modelIndex); + } else { + error |= QFile::remove(filePath(modelIndex)); + } + } + return error; +} + +/*! + Constructs a file system model with the given \a parent. +*/ +QFileSystemModel::QFileSystemModel(QObject *parent) + : QAbstractItemModel(*new QFileSystemModelPrivate, parent) +{ + Q_D(QFileSystemModel); + d->init(); +} + +/*! + \internal +*/ +QFileSystemModel::QFileSystemModel(QFileSystemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QFileSystemModel); + d->init(); +} + +/*! + Destroys this file system model. +*/ +QFileSystemModel::~QFileSystemModel() +{ +} + +/*! + \reimp +*/ +QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent)) + return QModelIndex(); + + // get the parent node + QFileSystemModelPrivate::QFileSystemNode *parentNode = (d->indexValid(parent) ? d->node(parent) : + const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&d->root)); + Q_ASSERT(parentNode); + + // now get the internal pointer for the index + QString childName = parentNode->visibleChildren[d->translateVisibleLocation(parentNode, row)]; + const QFileSystemModelPrivate::QFileSystemNode *indexNode = parentNode->children.value(childName); + Q_ASSERT(indexNode); + + return createIndex(row, column, const_cast<QFileSystemModelPrivate::QFileSystemNode*>(indexNode)); +} + +/*! + \overload + + Returns the model item index for the given \a path and \a column. +*/ +QModelIndex QFileSystemModel::index(const QString &path, int column) const +{ + Q_D(const QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *node = d->node(path, false); + QModelIndex idx = d->index(node); + if (idx.column() != column) + idx = idx.sibling(idx.row(), column); + return idx; +} + +/*! + \internal + + Return the QFileSystemNode that goes to index. + */ +QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const +{ + if (!index.isValid()) + return const_cast<QFileSystemNode*>(&root); + QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast<QFileSystemModelPrivate::QFileSystemNode*>(index.internalPointer()); + Q_ASSERT(indexNode); + return indexNode; +} + +#ifdef Q_OS_WIN32 +static QString qt_GetLongPathName(const QString &strShortPath) +{ + if (strShortPath.isEmpty() + || strShortPath == QLatin1String(".") || strShortPath == QLatin1String("..")) + return strShortPath; + if (strShortPath.length() == 2 && strShortPath.endsWith(QLatin1Char(':'))) + return strShortPath.toUpper(); + const QString absPath = QDir(strShortPath).absolutePath(); + if (absPath.startsWith(QLatin1String("//")) + || absPath.startsWith(QLatin1String("\\\\"))) // unc + return QDir::fromNativeSeparators(absPath); + if (absPath.startsWith(QLatin1Char('/'))) + return QString(); + const QString inputString = QLatin1String("\\\\?\\") + QDir::toNativeSeparators(absPath); + QVarLengthArray<TCHAR, MAX_PATH> buffer(MAX_PATH); + DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(), + buffer.data(), + buffer.size()); + if (result > DWORD(buffer.size())) { + buffer.resize(result); + result = ::GetLongPathName((wchar_t*)inputString.utf16(), + buffer.data(), + buffer.size()); + } + if (result > 4) { + QString longPath = QString::fromWCharArray(buffer.data() + 4); // ignoring prefix + longPath[0] = longPath.at(0).toUpper(); // capital drive letters + return QDir::fromNativeSeparators(longPath); + } else { + return QDir::fromNativeSeparators(strShortPath); + } +} +#endif + +/*! + \internal + + Given a path return the matching QFileSystemNode or &root if invalid +*/ +QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const +{ + Q_Q(const QFileSystemModel); + Q_UNUSED(q); + if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1Char(':'))) + return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + + // Construct the nodes up to the new root path if they need to be built + QString absolutePath; +#ifdef Q_OS_WIN32 + QString longPath = qt_GetLongPathName(path); +#else + QString longPath = path; +#endif + if (longPath == rootDir.path()) + absolutePath = rootDir.absolutePath(); + else + absolutePath = QDir(longPath).absolutePath(); + + // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const? + QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if ((pathElements.isEmpty()) +#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN) + && QDir::fromNativeSeparators(longPath) != QLatin1String("/") +#endif + ) + return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + QModelIndex index = QModelIndex(); // start with "My Computer" +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path + QString host = QLatin1String("\\\\") + pathElements.first(); + if (absolutePath == QDir::fromNativeSeparators(host)) + absolutePath.append(QLatin1Char('/')); + if (longPath.endsWith(QLatin1Char('/')) && !absolutePath.endsWith(QLatin1Char('/'))) + absolutePath.append(QLatin1Char('/')); + int r = 0; + QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + if (!root.children.contains(host.toLower())) { + if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/'))) + return rootNode; + QFileInfo info(host); + if (!info.exists()) + return rootNode; + QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this); + p->addNode(rootNode, host,info); + p->addVisibleFiles(rootNode, QStringList(host)); + } + r = rootNode->visibleLocation(host); + r = translateVisibleLocation(rootNode, r); + index = q->index(r, 0, QModelIndex()); + pathElements.pop_front(); + } else +#endif + +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + { + if (!pathElements.at(0).contains(QLatin1String(":"))) { + // The reason we express it like this instead of with anonymous, temporary + // variables, is to workaround a compiler crash with Q_CC_NOKIAX86. + QString rootPath = QDir(longPath).rootPath(); + pathElements.prepend(rootPath); + } + if (pathElements.at(0).endsWith(QLatin1Char('/'))) + pathElements[0].chop(1); + } +#else + // add the "/" item, since it is a valid path element on Unix + if (absolutePath[0] == QLatin1Char('/')) + pathElements.prepend(QLatin1String("/")); +#endif + + QFileSystemModelPrivate::QFileSystemNode *parent = node(index); + + for (int i = 0; i < pathElements.count(); ++i) { + QString element = pathElements.at(i); +#ifdef Q_OS_WIN + // On Windows, "filename......." and "filename" are equivalent Task #133928 + while (element.endsWith(QLatin1Char('.'))) + element.chop(1); +#endif + bool alreadyExisted = parent->children.contains(element); + + // we couldn't find the path element, we create a new node since we + // _know_ that the path is valid + if (alreadyExisted) { + if ((parent->children.count() == 0) + || (parent->caseSensitive() + && parent->children.value(element)->fileName != element) + || (!parent->caseSensitive() + && parent->children.value(element)->fileName.toLower() != element.toLower())) + alreadyExisted = false; + } + + QFileSystemModelPrivate::QFileSystemNode *node; + if (!alreadyExisted) { + // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"), + // a path that doesn't exists, I.E. don't blindly create directories. + QFileInfo info(absolutePath); + if (!info.exists()) + return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this); + node = p->addNode(parent, element,info); +#ifndef QT_NO_FILESYSTEMWATCHER + node->populate(fileInfoGatherer.getInfo(info)); +#endif + } else { + node = parent->children.value(element); + } + + Q_ASSERT(node); + if (!node->isVisible) { + // It has been filtered out + if (alreadyExisted && node->hasInformation() && !fetch) + return const_cast<QFileSystemModelPrivate::QFileSystemNode*>(&root); + + QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this); + p->addVisibleFiles(parent, QStringList(element)); + if (!p->bypassFilters.contains(node)) + p->bypassFilters[node] = 1; + QString dir = q->filePath(this->index(parent)); + if (!node->hasInformation() && fetch) { + Fetching f; + f.dir = dir; + f.file = element; + f.node = node; + p->toFetch.append(f); + p->fetchingTimer.start(0, const_cast<QFileSystemModel*>(q)); + } + } + parent = node; + } + + return parent; +} + +/*! + \reimp +*/ +void QFileSystemModel::timerEvent(QTimerEvent *event) +{ + Q_D(QFileSystemModel); + if (event->timerId() == d->fetchingTimer.timerId()) { + d->fetchingTimer.stop(); +#ifndef QT_NO_FILESYSTEMWATCHER + for (int i = 0; i < d->toFetch.count(); ++i) { + const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node; + if (!node->hasInformation()) { + d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir, + QStringList(d->toFetch.at(i).file)); + } else { + // qDebug() << "yah!, you saved a little gerbil soul"; + } + } +#endif + d->toFetch.clear(); + } +} + +/*! + Returns true if the model item \a index represents a directory; + otherwise returns false. +*/ +bool QFileSystemModel::isDir(const QModelIndex &index) const +{ + // This function is for public usage only because it could create a file info + Q_D(const QFileSystemModel); + if (!index.isValid()) + return true; + QFileSystemModelPrivate::QFileSystemNode *n = d->node(index); + if (n->hasInformation()) + return n->isDir(); + return fileInfo(index).isDir(); +} + +/*! + Returns the size in bytes of \a index. If the file does not exist, 0 is returned. + */ +qint64 QFileSystemModel::size(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return 0; + return d->node(index)->size(); +} + +/*! + Returns the type of file \a index such as "Directory" or "JPEG file". + */ +QString QFileSystemModel::type(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return QString(); + return d->node(index)->type(); +} + +/*! + Returns the date and time when \a index was last modified. + */ +QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return QDateTime(); + return d->node(index)->lastModified(); +} + +/*! + \reimp +*/ +QModelIndex QFileSystemModel::parent(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!d->indexValid(index)) + return QModelIndex(); + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index); + Q_ASSERT(indexNode != 0); + QFileSystemModelPrivate::QFileSystemNode *parentNode = (indexNode ? indexNode->parent : 0); + if (parentNode == 0 || parentNode == &d->root) + return QModelIndex(); + + // get the parent's row + QFileSystemModelPrivate::QFileSystemNode *grandParentNode = parentNode->parent; + Q_ASSERT(grandParentNode->children.contains(parentNode->fileName)); + int visualRow = d->translateVisibleLocation(grandParentNode, grandParentNode->visibleLocation(grandParentNode->children.value(parentNode->fileName)->fileName)); + if (visualRow == -1) + return QModelIndex(); + return createIndex(visualRow, 0, parentNode); +} + +/* + \internal + + return the index for node +*/ +QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node) const +{ + Q_Q(const QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : 0); + if (node == &root || !parentNode) + return QModelIndex(); + + // get the parent's row + Q_ASSERT(node); + if (!node->isVisible) + return QModelIndex(); + + int visualRow = translateVisibleLocation(parentNode, parentNode->visibleLocation(node->fileName)); + return q->createIndex(visualRow, 0, const_cast<QFileSystemNode*>(node)); +} + +/*! + \reimp +*/ +bool QFileSystemModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (parent.column() > 0) + return false; + + if (!parent.isValid()) // drives + return true; + + const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + Q_ASSERT(indexNode); + return (indexNode->isDir()); +} + +/*! + \reimp + */ +bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + return (!indexNode->populatedChildren); +} + +/*! + \reimp + */ +void QFileSystemModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QFileSystemModel); + if (!d->setRootPath) + return; + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + if (indexNode->populatedChildren) + return; + indexNode->populatedChildren = true; + d->fileInfoGatherer.list(filePath(parent)); +} + +/*! + \reimp +*/ +int QFileSystemModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + return d->root.visibleChildren.count(); + + const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent); + return parentNode->visibleChildren.count(); +} + +/*! + \reimp +*/ +int QFileSystemModel::columnCount(const QModelIndex &parent) const +{ + return (parent.column() > 0) ? 0 : 4; +} + +/*! + Returns the data stored under the given \a role for the item "My Computer". + + \sa Qt::ItemDataRole + */ +QVariant QFileSystemModel::myComputer(int role) const +{ + Q_D(const QFileSystemModel); + switch (role) { + case Qt::DisplayRole: + return d->myComputer(); + case Qt::DecorationRole: + return d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Computer); + } + return QVariant(); +} + +/*! + \reimp +*/ +QVariant QFileSystemModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid() || index.model() != this) + return QVariant(); + + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + switch (index.column()) { + case 0: return d->displayName(index); + case 1: return d->size(index); + case 2: return d->type(index); + case 3: return d->time(index); + default: + qWarning("data: invalid display value column %d", index.column()); + break; + } + break; + case FilePathRole: + return filePath(index); + case FileNameRole: + return d->name(index); + case Qt::DecorationRole: + if (index.column() == 0) { + QIcon icon = d->icon(index); + if (icon.isNull()) { + if (d->node(index)->isDir()) + icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Folder); + else + icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::File); + } + return icon; + } + break; + case Qt::TextAlignmentRole: + if (index.column() == 1) + return Qt::AlignRight; + break; + case FilePermissions: + int p = permissions(index); + return p; + } + + return QVariant(); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::size(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + const QFileSystemNode *n = node(index); + if (n->isDir()) { +#ifdef Q_OS_MAC + return QLatin1String("--"); +#else + return QLatin1String(""); +#endif + // Windows - "" + // OS X - "--" + // Konqueror - "4 KB" + // Nautilus - "9 items" (the number of children) + } + return size(n->size()); +} + +QString QFileSystemModelPrivate::size(qint64 bytes) +{ + // According to the Si standard KB is 1000 bytes, KiB is 1024 + // but on windows sizes are calculated by dividing by 1024 so we do what they do. + const qint64 kb = 1024; + const qint64 mb = 1024 * kb; + const qint64 gb = 1024 * mb; + const qint64 tb = 1024 * gb; + if (bytes >= tb) + return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); + if (bytes >= gb) + return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); + if (bytes >= mb) + return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); + if (bytes >= kb) + return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); + return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes)); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::time(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); +#ifndef QT_NO_DATESTRING + return node(index)->lastModified().toString(Qt::SystemLocaleDate); +#else + Q_UNUSED(index); + return QString(); +#endif +} + +/* + \internal +*/ +QString QFileSystemModelPrivate::type(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + return node(index)->type(); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::name(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + QFileSystemNode *dirNode = node(index); + if (dirNode->isSymLink() && fileInfoGatherer.resolveSymlinks()) { + QString fullPath = QDir::fromNativeSeparators(filePath(index)); + if (resolvedSymLinks.contains(fullPath)) + return resolvedSymLinks[fullPath]; + } + return dirNode->fileName; +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::displayName(const QModelIndex &index) const +{ +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QFileSystemNode *dirNode = node(index); + if (!dirNode->volumeName.isNull()) + return dirNode->volumeName + QLatin1String(" (") + name(index) + QLatin1Char(')'); +#endif + return name(index); +} + +/*! + \internal +*/ +QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const +{ + if (!index.isValid()) + return QIcon(); + return node(index)->icon(); +} + +/*! + \reimp +*/ +bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + Q_D(QFileSystemModel); + if (!idx.isValid() + || idx.column() != 0 + || role != Qt::EditRole + || (flags(idx) & Qt::ItemIsEditable) == 0) { + return false; + } + + QString newName = value.toString(); + QString oldName = idx.data().toString(); + if (newName == idx.data().toString()) + return true; + + if (newName.isEmpty() + || newName.contains(QDir::separator()) + || !QDir(filePath(parent(idx))).rename(oldName, newName)) { +#ifndef QT_NO_MESSAGEBOX + QMessageBox::information(0, QFileSystemModel::tr("Invalid filename"), + QFileSystemModel::tr("<b>The name \"%1\" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks.") + .arg(newName), + QMessageBox::Ok); +#endif // QT_NO_MESSAGEBOX + return false; + } else { + /* + *After re-naming something we don't want the selection to change* + - can't remove rows and later insert + - can't quickly remove and insert + - index pointer can't change because treeview doesn't use persistant index's + + - if this get any more complicated think of changing it to just + use layoutChanged + */ + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(idx); + QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent; + int visibleLocation = parentNode->visibleLocation(parentNode->children.value(indexNode->fileName)->fileName); + + d->addNode(parentNode, newName,indexNode->info->fileInfo()); + parentNode->visibleChildren.removeAt(visibleLocation); + QFileSystemModelPrivate::QFileSystemNode * oldValue = parentNode->children.value(oldName); + parentNode->children[newName] = oldValue; + QFileInfo info(d->rootDir, newName); + oldValue->fileName = newName; + oldValue->parent = parentNode; + oldValue->populate(d->fileInfoGatherer.getInfo(info)); + oldValue->isVisible = true; + + parentNode->children.remove(oldName); + parentNode->visibleChildren.insert(visibleLocation, newName); + + d->delayedSort(); + emit fileRenamed(filePath(idx.parent()), oldName, newName); + } + return true; +} + +/*! + \reimp +*/ +QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DecorationRole: + if (section == 0) { + // ### TODO oh man this is ugly and doesn't even work all the way! + // it is still 2 pixels off + QImage pixmap(16, 1, QImage::Format_Mono); + pixmap.fill(0); + pixmap.setAlphaChannel(pixmap.createAlphaMask()); + return pixmap; + } + break; + case Qt::TextAlignmentRole: + return Qt::AlignLeft; + } + + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QAbstractItemModel::headerData(section, orientation, role); + + QString returnValue; + switch (section) { + case 0: returnValue = tr("Name"); + break; + case 1: returnValue = tr("Size"); + break; + case 2: returnValue = +#ifdef Q_OS_MAC + tr("Kind", "Match OS X Finder"); +#else + tr("Type", "All other platforms"); +#endif + break; + // Windows - Type + // OS X - Kind + // Konqueror - File Type + // Nautilus - Type + case 3: returnValue = tr("Date Modified"); + break; + default: return QVariant(); + } + return returnValue; +} + +/*! + \reimp +*/ +Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (!index.isValid()) + return flags; + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index); + if (d->nameFilterDisables && !d->passNameFilters(indexNode)) { + flags &= ~Qt::ItemIsEnabled; + // ### TODO you shouldn't be able to set this as the current item, task 119433 + return flags; + } + + flags |= Qt::ItemIsDragEnabled; + if (d->readOnly) + return flags; + if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) { + flags |= Qt::ItemIsEditable; + if (indexNode->isDir()) + flags |= Qt::ItemIsDropEnabled; + } + return flags; +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::_q_performDelayedSort() +{ + Q_Q(QFileSystemModel); + q->sort(sortColumn, sortOrder); +} + +static inline QChar getNextChar(const QString &s, int location) +{ + return (location < s.length()) ? s.at(location) : QChar(); +} + +/*! + Natural number sort, skips spaces. + + Examples: + 1, 2, 10, 55, 100 + 01.jpg, 2.jpg, 10.jpg + + Note on the algorithm: + Only as many characters as necessary are looked at and at most they all + are looked at once. + + Slower then QString::compare() (of course) + */ +int QFileSystemModelPrivate::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) +{ + for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) { + // skip spaces, tabs and 0's + QChar c1 = getNextChar(s1, l1); + while (c1.isSpace()) + c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); + while (c2.isSpace()) + c2 = getNextChar(s2, ++l2); + + if (c1.isDigit() && c2.isDigit()) { + while (c1.digitValue() == 0) + c1 = getNextChar(s1, ++l1); + while (c2.digitValue() == 0) + c2 = getNextChar(s2, ++l2); + + int lookAheadLocation1 = l1; + int lookAheadLocation2 = l2; + int currentReturnValue = 0; + // find the last digit, setting currentReturnValue as we go if it isn't equal + for ( + QChar lookAhead1 = c1, lookAhead2 = c2; + (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), + lookAhead2 = getNextChar(s2, ++lookAheadLocation2) + ) { + bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); + bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); + if (!is1ADigit && !is2ADigit) + break; + if (!is1ADigit) + return -1; + if (!is2ADigit) + return 1; + if (currentReturnValue == 0) { + if (lookAhead1 < lookAhead2) { + currentReturnValue = -1; + } else if (lookAhead1 > lookAhead2) { + currentReturnValue = 1; + } + } + } + if (currentReturnValue != 0) + return currentReturnValue; + } + + if (cs == Qt::CaseInsensitive) { + if (!c1.isLower()) c1 = c1.toLower(); + if (!c2.isLower()) c2 = c2.toLower(); + } + int r = QString::localeAwareCompare(c1, c2); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + // The two strings are the same (02 == 2) so fall back to the normal sort + return QString::compare(s1, s2, cs); +} + +/* + \internal + Helper functor used by sort() +*/ +class QFileSystemModelSorter +{ +public: + inline QFileSystemModelSorter(int column) : sortColumn(column) {} + + bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l, + const QFileSystemModelPrivate::QFileSystemNode *r) const + { + switch (sortColumn) { + case 0: { +#ifndef Q_OS_MAC + // place directories before files + bool left = l->isDir(); + bool right = r->isDir(); + if (left ^ right) + return left; +#endif + return QFileSystemModelPrivate::naturalCompare(l->fileName, + r->fileName, Qt::CaseInsensitive) < 0; + } + case 1: + // Directories go first + if (l->isDir() && !r->isDir()) + return true; + return l->size() < r->size(); + case 2: + return l->type() < r->type(); + case 3: + return l->lastModified() < r->lastModified(); + } + Q_ASSERT(false); + return false; + } + + bool operator()(const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &l, + const QPair<QFileSystemModelPrivate::QFileSystemNode*, int> &r) const + { + return compareNodes(l.first, r.first); + } + + +private: + int sortColumn; +}; + +/* + \internal + + Sort all of the children of parent +*/ +void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent) +{ + Q_Q(QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent); + if (indexNode->children.count() == 0) + return; + + QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > values; + QHash<QString, QFileSystemNode *>::const_iterator iterator; + int i = 0; + for(iterator = indexNode->children.begin() ; iterator != indexNode->children.end() ; ++iterator) { + if (filtersAcceptsNode(iterator.value())) { + values.append(QPair<QFileSystemModelPrivate::QFileSystemNode*, int>((iterator.value()), i)); + } else { + iterator.value()->isVisible = false; + } + i++; + } + QFileSystemModelSorter ms(column); + qStableSort(values.begin(), values.end(), ms); + // First update the new visible list + indexNode->visibleChildren.clear(); + //No more dirty item we reset our internal dirty index + indexNode->dirtyChildrenIndex = -1; + for (int i = 0; i < values.count(); ++i) { + indexNode->visibleChildren.append(values.at(i).first->fileName); + values.at(i).first->isVisible = true; + } + + if (!disableRecursiveSort) { + for (int i = 0; i < q->rowCount(parent); ++i) { + const QModelIndex childIndex = q->index(i, 0, parent); + QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex); + //Only do a recursive sort on visible nodes + if (indexNode->isVisible) + sortChildren(column, childIndex); + } + } +} + +/*! + \reimp +*/ +void QFileSystemModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QFileSystemModel); + if (d->sortOrder == order && d->sortColumn == column && !d->forceSort) + return; + + emit layoutAboutToBeChanged(); + QModelIndexList oldList = persistentIndexList(); + QList<QPair<QFileSystemModelPrivate::QFileSystemNode*, int> > oldNodes; + for (int i = 0; i < oldList.count(); ++i) { + QPair<QFileSystemModelPrivate::QFileSystemNode*, int> pair(d->node(oldList.at(i)), oldList.at(i).column()); + oldNodes.append(pair); + } + + if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) { + //we sort only from where we are, don't need to sort all the model + d->sortChildren(column, index(rootPath())); + d->sortColumn = column; + d->forceSort = false; + } + d->sortOrder = order; + + QModelIndexList newList; + for (int i = 0; i < oldNodes.count(); ++i) { + QModelIndex idx = d->index(oldNodes.at(i).first); + idx = idx.sibling(idx.row(), oldNodes.at(i).second); + newList.append(idx); + } + changePersistentIndexList(oldList, newList); + emit layoutChanged(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of items + in the model. +*/ +QStringList QFileSystemModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + Returns an object that contains a serialized description of the specified + \a indexes. The format used to describe the items corresponding to the + indexes is obtained from the mimeTypes() function. + + If the list of indexes is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QUrl> urls; + QList<QModelIndex>::const_iterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + if ((*it).column() == 0) + urls << QUrl::fromLocalFile(filePath(*it)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action over the row in the model specified by the \a row and + \a column and by the \a parent index. + + \sa supportedDropActions() +*/ +bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_UNUSED(row); + Q_UNUSED(column); + if (!parent.isValid() || isReadOnly()) + return false; + + bool success = true; + QString to = filePath(parent) + QDir::separator(); + + QList<QUrl> urls = data->urls(); + QList<QUrl>::const_iterator it = urls.constBegin(); + + switch (action) { + case Qt::CopyAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::copy(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::LinkAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::link(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::MoveAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::rename(path, to + QFileInfo(path).fileName()) && success; + } + break; + default: + return false; + } + + return success; +} + +/*! + \reimp +*/ +Qt::DropActions QFileSystemModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; +} + +/*! + Returns the path of the item stored in the model under the + \a index given. +*/ +QString QFileSystemModel::filePath(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + QString fullPath = d->filePath(index); + QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index); + if (dirNode->isSymLink() && d->fileInfoGatherer.resolveSymlinks() + && d->resolvedSymLinks.contains(fullPath) + && dirNode->isDir()) { + QFileInfo resolvedInfo(fullPath); + resolvedInfo = resolvedInfo.canonicalFilePath(); + if (resolvedInfo.exists()) + return resolvedInfo.filePath(); + } + return fullPath; +} + +QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const +{ + Q_Q(const QFileSystemModel); + Q_UNUSED(q); + if (!index.isValid()) + return QString(); + Q_ASSERT(index.model() == q); + + QStringList path; + QModelIndex idx = index; + while (idx.isValid()) { + QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx); + if (dirNode) + path.prepend(dirNode->fileName); + idx = idx.parent(); + } + QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator())); +#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) + if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/')) + fullPath = fullPath.mid(1); +#endif +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (fullPath.length() == 2 && fullPath.endsWith(QLatin1Char(':'))) + fullPath.append(QLatin1Char('/')); +#endif + return fullPath; +} + +/*! + Create a directory with the \a name in the \a parent model index. +*/ +QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name) +{ + Q_D(QFileSystemModel); + if (!parent.isValid()) + return parent; + + QDir dir(filePath(parent)); + if (!dir.mkdir(name)) + return QModelIndex(); + QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent); + d->addNode(parentNode, name, QFileInfo()); + Q_ASSERT(parentNode->children.contains(name)); + QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name]; + node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); + d->addVisibleFiles(parentNode, QStringList(name)); + return d->index(node); +} + +/*! + Returns the complete OR-ed together combination of QFile::Permission for the \a index. + */ +QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + QFile::Permissions p = d->node(index)->permissions(); + if (d->readOnly) { + p ^= (QFile::WriteOwner | QFile::WriteUser + | QFile::WriteGroup | QFile::WriteOther); + } + return p; +} + +/*! + Sets the directory that is being watched by the model to \a newPath by + installing a \l{QFileSystemWatcher}{file system watcher} on it. Any + changes to files and directories within this directory will be + reflected in the model. + + If the path is changed, the rootPathChanged() signal will be emitted. + + \note This function does not change the structure of the model or + modify the data available to views. In other words, the "root" of + the model is \e not changed to include only files and directories + within the directory specified by \a newPath in the file system. + */ +QModelIndex QFileSystemModel::setRootPath(const QString &newPath) +{ + Q_D(QFileSystemModel); +#ifdef Q_OS_WIN +#ifdef Q_OS_WIN32 + QString longNewPath = qt_GetLongPathName(newPath); +#else + QString longNewPath = QDir::fromNativeSeparators(newPath); +#endif +#else + QString longNewPath = newPath; +#endif + QDir newPathDir(longNewPath); + //we remove .. and . from the given path if exist + if (!newPath.isEmpty()) { + longNewPath = QDir::cleanPath(longNewPath); + newPathDir.setPath(longNewPath); + } + + d->setRootPath = true; + + //user don't ask for the root path ("") but the conversion failed + if (!newPath.isEmpty() && longNewPath.isEmpty()) + return d->index(rootPath()); + + if (d->rootDir.path() == longNewPath) + return d->index(rootPath()); + + bool showDrives = (longNewPath.isEmpty() || longNewPath == d->myComputer()); + if (!showDrives && !newPathDir.exists()) + return d->index(rootPath()); + + //We remove the watcher on the previous path + if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) { + //This remove the watcher for the old rootPath + d->fileInfoGatherer.removePath(rootPath()); + //This line "marks" the node as dirty, so the next fetchMore + //call on the path will ask the gatherer to install a watcher again + //But it doesn't re-fetch everything + d->node(rootPath())->populatedChildren = false; + } + + // We have a new valid root path + d->rootDir = newPathDir; + QModelIndex newRootIndex; + if (showDrives) { + // otherwise dir will become '.' + d->rootDir.setPath(QLatin1String("")); + } else { + newRootIndex = d->index(newPathDir.path()); + } + fetchMore(newRootIndex); + emit rootPathChanged(longNewPath); + d->forceSort = true; + d->delayedSort(); + return newRootIndex; +} + +/*! + The currently set root path + + \sa rootDirectory() +*/ +QString QFileSystemModel::rootPath() const +{ + Q_D(const QFileSystemModel); + return d->rootDir.path(); +} + +/*! + The currently set directory + + \sa rootPath() +*/ +QDir QFileSystemModel::rootDirectory() const +{ + Q_D(const QFileSystemModel); + QDir dir(d->rootDir); + dir.setNameFilters(nameFilters()); + dir.setFilter(filter()); + return dir; +} + +/*! + Sets the \a provider of file icons for the directory model. +*/ +void QFileSystemModel::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QFileSystemModel); + d->fileInfoGatherer.setIconProvider(provider); + d->root.updateIcon(provider, QString()); +} + +/*! + Returns the file icon provider for this directory model. +*/ +QFileIconProvider *QFileSystemModel::iconProvider() const +{ + Q_D(const QFileSystemModel); + return d->fileInfoGatherer.iconProvider(); +} + +/*! + Sets the directory model's filter to that specified by \a filters. + + Note that the filter you set should always include the QDir::AllDirs enum value, + otherwise QFileSystemModel won't be able to read the directory structure. + + \sa QDir::Filters +*/ +void QFileSystemModel::setFilter(QDir::Filters filters) +{ + Q_D(QFileSystemModel); + if (d->filters == filters) + return; + d->filters = filters; + // CaseSensitivity might have changed + setNameFilters(nameFilters()); + d->forceSort = true; + d->delayedSort(); +} + +/*! + Returns the filter specified for the directory model. + + If a filter has not been set, the default filter is QDir::AllEntries | + QDir::NoDotAndDotDot | QDir::AllDirs. + + \sa QDir::Filters +*/ +QDir::Filters QFileSystemModel::filter() const +{ + Q_D(const QFileSystemModel); + return d->filters; +} + +/*! + \property QFileSystemModel::resolveSymlinks + \brief Whether the directory model should resolve symbolic links + + This is only relevant on operating systems that support symbolic links. + + By default, this property is false. +*/ +void QFileSystemModel::setResolveSymlinks(bool enable) +{ + Q_D(QFileSystemModel); + d->fileInfoGatherer.setResolveSymlinks(enable); +} + +bool QFileSystemModel::resolveSymlinks() const +{ + Q_D(const QFileSystemModel); + return d->fileInfoGatherer.resolveSymlinks(); +} + +/*! + \property QFileSystemModel::readOnly + \brief Whether the directory model allows writing to the file system + + If this property is set to false, the directory model will allow renaming, copying + and deleting of files and directories. + + This property is true by default +*/ +void QFileSystemModel::setReadOnly(bool enable) +{ + Q_D(QFileSystemModel); + d->readOnly = enable; +} + +bool QFileSystemModel::isReadOnly() const +{ + Q_D(const QFileSystemModel); + return d->readOnly; +} + +/*! + \property QFileSystemModel::nameFilterDisables + \brief Whether files that don't pass the name filter are hidden or disabled + + This property is true by default +*/ +void QFileSystemModel::setNameFilterDisables(bool enable) +{ + Q_D(QFileSystemModel); + if (d->nameFilterDisables == enable) + return; + d->nameFilterDisables = enable; + d->forceSort = true; + d->delayedSort(); +} + +bool QFileSystemModel::nameFilterDisables() const +{ + Q_D(const QFileSystemModel); + return d->nameFilterDisables; +} + +/*! + Sets the name \a filters to apply against the existing files. +*/ +void QFileSystemModel::setNameFilters(const QStringList &filters) +{ + // Prep the regexp's ahead of time +#ifndef QT_NO_REGEXP + Q_D(QFileSystemModel); + + if (!d->bypassFilters.isEmpty()) { + // update the bypass filter to only bypass the stuff that must be kept around + d->bypassFilters.clear(); + // We guarantee that rootPath will stick around + QPersistentModelIndex root(index(rootPath())); + QModelIndexList persistantList = persistentIndexList(); + for (int i = 0; i < persistantList.count(); ++i) { + QFileSystemModelPrivate::QFileSystemNode *node; + node = d->node(persistantList.at(i)); + while (node) { + if (d->bypassFilters.contains(node)) + break; + if (node->isDir()) + d->bypassFilters[node] = true; + node = node->parent; + } + } + } + + d->nameFilters.clear(); + const Qt::CaseSensitivity caseSensitive = + (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; + for (int i = 0; i < filters.size(); ++i) { + d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard); + } + d->forceSort = true; + d->delayedSort(); +#endif +} + +/*! + Returns a list of filters applied to the names in the model. +*/ +QStringList QFileSystemModel::nameFilters() const +{ + Q_D(const QFileSystemModel); + QStringList filters; +#ifndef QT_NO_REGEXP + for (int i = 0; i < d->nameFilters.size(); ++i) { + filters << d->nameFilters.at(i).pattern(); + } +#endif + return filters; +} + +/*! + \reimp +*/ +bool QFileSystemModel::event(QEvent *event) +{ + Q_D(QFileSystemModel); + if (event->type() == QEvent::LanguageChange) { + d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString()); + return true; + } + return QAbstractItemModel::event(event); +} + +bool QFileSystemModel::rmdir(const QModelIndex &aindex) const +{ + QString path = filePath(aindex); + QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func()); + d->fileInfoGatherer.removePath(path); + return QDir().rmdir(path); +} + +/*! + \internal + + Performed quick listing and see if any files have been added or removed, + then fetch more information on visible files. + */ +void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files) +{ + QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false); + if (parentNode->children.count() == 0) + return; + QStringList toRemove; +#if defined(Q_OS_SYMBIAN) + // Filename case must be exact in qBinaryFind below, so create a list of all lowercase names. + QStringList newFiles; + for(int i = 0; i < files.size(); i++) { + newFiles << files.at(i).toLower(); + } +#else + QStringList newFiles = files; +#endif + qSort(newFiles.begin(), newFiles.end()); + QHash<QString, QFileSystemNode*>::const_iterator i = parentNode->children.constBegin(); + while (i != parentNode->children.constEnd()) { + QStringList::iterator iterator; + iterator = qBinaryFind(newFiles.begin(), newFiles.end(), +#if defined(Q_OS_SYMBIAN) + i.value()->fileName.toLower()); +#else + i.value()->fileName); +#endif + if (iterator == newFiles.end()) { + toRemove.append(i.value()->fileName); + } + ++i; + } + for (int i = 0 ; i < toRemove.count() ; ++i ) + removeNode(parentNode, toRemove[i]); +} + +/*! + \internal + + Adds a new file to the children of parentNode + + *WARNING* this will change the count of children +*/ +QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info) +{ + // In the common case, itemLocation == count() so check there first + QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode); +#ifndef QT_NO_FILESYSTEMWATCHER + node->populate(info); +#endif +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + //The parentNode is "" so we are listing the drives + if (parentNode->fileName.isEmpty()) { + wchar_t name[MAX_PATH + 1]; + //GetVolumeInformation requires to add trailing backslash + const QString nodeName = fileName + QLatin1String("\\"); + BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()), + name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0); + if (success && name[0]) + node->volumeName = QString::fromWCharArray(name); + } +#endif + parentNode->children.insert(fileName, node); + return node; +} + +/*! + \internal + + File at parentNode->children(itemLocation) has been removed, remove from the lists + and emit signals if necessary + + *WARNING* this will change the count of children and could change visibleChildren + */ +void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name) +{ + Q_Q(QFileSystemModel); + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + + int vLocation = parentNode->visibleLocation(name); + if (vLocation >= 0 && !indexHidden) + q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation), + translateVisibleLocation(parentNode, vLocation)); + QFileSystemNode * node = parentNode->children.take(name); + delete node; + // cleanup sort files after removing rather then re-sorting which is O(n) + if (vLocation >= 0) + parentNode->visibleChildren.removeAt(vLocation); + if (vLocation >= 0 && !indexHidden) + q->endRemoveRows(); +} + +/* + \internal + Helper functor used by addVisibleFiles() +*/ +class QFileSystemModelVisibleFinder +{ +public: + inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {} + + bool operator()(const QString &, QString r) const + { + return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r)); + } + + QString name; +private: + QFileSystemModelPrivate::QFileSystemNode *parentNode; + QFileSystemModelSorter *sorter; +}; + +/*! + \internal + + File at parentNode->children(itemLocation) was not visible before, but now should be + and emit signals if necessary. + + *WARNING* this will change the visible count + */ +void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles) +{ + Q_Q(QFileSystemModel); + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + if (!indexHidden) { + q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1); + } + + if (parentNode->dirtyChildrenIndex == -1) + parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count(); + + for (int i = 0; i < newFiles.count(); ++i) { + parentNode->visibleChildren.append(newFiles.at(i)); + parentNode->children[newFiles.at(i)]->isVisible = true; + } + if (!indexHidden) + q->endInsertRows(); +} + +/*! + \internal + + File was visible before, but now should NOT be + + *WARNING* this will change the visible count + */ +void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation) +{ + Q_Q(QFileSystemModel); + if (vLocation == -1) + return; + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + if (!indexHidden) + q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation), + translateVisibleLocation(parentNode, vLocation)); + parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false; + parentNode->visibleChildren.removeAt(vLocation); + if (!indexHidden) + q->endRemoveRows(); +} + +/*! + \internal + + The thread has received new information about files, + update and emit dataChanged if it has actually changed. + */ +void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &updates) +{ + Q_Q(QFileSystemModel); + QVector<QString> rowsToUpdate; + QStringList newFiles; + QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false); + QModelIndex parentIndex = index(parentNode); + for (int i = 0; i < updates.count(); ++i) { + QString fileName = updates.at(i).first; + Q_ASSERT(!fileName.isEmpty()); + QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second); + bool previouslyHere = parentNode->children.contains(fileName); + if (!previouslyHere) { + addNode(parentNode, fileName, info.fileInfo()); + } + QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName); + bool isCaseSensitive = parentNode->caseSensitive(); + if (isCaseSensitive) { + if (node->fileName != fileName) + continue; + } else { + if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0) + continue; + } + if (isCaseSensitive) { + Q_ASSERT(node->fileName == fileName); + } else { + node->fileName = fileName; + } + + if (info.size() == -1 && !info.isSymLink()) { + removeNode(parentNode, fileName); + continue; + } + if (*node != info ) { + node->populate(info); + bypassFilters.remove(node); + // brand new information. + if (filtersAcceptsNode(node)) { + if (!node->isVisible) { + newFiles.append(fileName); + } else { + rowsToUpdate.append(fileName); + } + } else { + if (node->isVisible) { + int visibleLocation = parentNode->visibleLocation(fileName); + removeVisibleFile(parentNode, visibleLocation); + } else { + // The file is not visible, don't do anything + } + } + } + } + + // bundle up all of the changed signals into as few as possible. + qSort(rowsToUpdate.begin(), rowsToUpdate.end()); + QString min; + QString max; + for (int i = 0; i < rowsToUpdate.count(); ++i) { + QString value = rowsToUpdate.at(i); + //##TODO is there a way to bundle signals with QString as the content of the list? + /*if (min.isEmpty()) { + min = value; + if (i != rowsToUpdate.count() - 1) + continue; + } + if (i != rowsToUpdate.count() - 1) { + if ((value == min + 1 && max.isEmpty()) || value == max + 1) { + max = value; + continue; + } + }*/ + max = value; + min = value; + int visibleMin = parentNode->visibleLocation(min); + int visibleMax = parentNode->visibleLocation(max); + if (visibleMin >= 0 + && visibleMin < parentNode->visibleChildren.count() + && parentNode->visibleChildren.at(visibleMin) == min + && visibleMax >= 0) { + QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex); + QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex); + emit q->dataChanged(bottom, top); + } + + /*min = QString(); + max = QString();*/ + } + + if (newFiles.count() > 0) { + addVisibleFiles(parentNode, newFiles); + } + + if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) { + forceSort = true; + delayedSort(); + } +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName) +{ + resolvedSymLinks[fileName] = resolvedName; +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::init() +{ + Q_Q(QFileSystemModel); + qRegisterMetaType<QList<QPair<QString,QFileInfo> > >("QList<QPair<QString,QFileInfo> >"); + q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), + q, SLOT(_q_directoryChanged(QString,QStringList))); + q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList<QPair<QString,QFileInfo> >)), + q, SLOT(_q_fileSystemChanged(QString,QList<QPair<QString,QFileInfo> >))); + q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), + q, SLOT(_q_resolvedName(QString,QString))); + q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), + q, SIGNAL(directoryLoaded(QString))); + q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection); + + QHash<int, QByteArray> roles = q->roleNames(); + roles.insertMulti(QFileSystemModel::FileIconRole, "fileIcon"); // == Qt::decoration + roles.insert(QFileSystemModel::FilePathRole, "filePath"); + roles.insert(QFileSystemModel::FileNameRole, "fileName"); + roles.insert(QFileSystemModel::FilePermissions, "filePermissions"); + q->setRoleNames(roles); +} + +/*! + \internal + + Returns false if node doesn't pass the filters otherwise true + + QDir::Modified is not supported + QDir::Drives is not supported +*/ +bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const +{ + // always accept drives + if (node->parent == &root || bypassFilters.contains(node)) + return true; + + // If we don't know anything yet don't accept it + if (!node->hasInformation()) + return false; + + const bool filterPermissions = ((filters & QDir::PermissionMask) + && (filters & QDir::PermissionMask) != QDir::PermissionMask); + const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); + const bool hideFiles = !(filters & QDir::Files); + const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable)); + const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable)); + const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable)); + const bool hideHidden = !(filters & QDir::Hidden); + const bool hideSystem = !(filters & QDir::System); + const bool hideSymlinks = (filters & QDir::NoSymLinks); + const bool hideDot = (filters & QDir::NoDot) || (filters & QDir::NoDotAndDotDot); // ### Qt5: simplify (because NoDotAndDotDot=NoDot|NoDotDot) + const bool hideDotDot = (filters & QDir::NoDotDot) || (filters & QDir::NoDotAndDotDot); // ### Qt5: simplify (because NoDotAndDotDot=NoDot|NoDotDot) + + // Note that we match the behavior of entryList and not QFileInfo on this and this + // incompatibility won't be fixed until Qt 5 at least + bool isDot = (node->fileName == QLatin1String(".")); + bool isDotDot = (node->fileName == QLatin1String("..")); + if ( (hideHidden && !(isDot || isDotDot) && node->isHidden()) + || (hideSystem && node->isSystem()) + || (hideDirs && node->isDir()) + || (hideFiles && node->isFile()) + || (hideSymlinks && node->isSymLink()) + || (hideReadable && node->isReadable()) + || (hideWritable && node->isWritable()) + || (hideExecutable && node->isExecutable()) + || (hideDot && isDot) + || (hideDotDot && isDotDot)) + return false; + + return nameFilterDisables || passNameFilters(node); +} + +/* + \internal + + Returns true if node passes the name filters and should be visible. + */ +bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const +{ +#ifndef QT_NO_REGEXP + if (nameFilters.isEmpty()) + return true; + + // Check the name regularexpression filters + if (!(node->isDir() && (filters & QDir::AllDirs))) { + for (int i = 0; i < nameFilters.size(); ++i) { + if (nameFilters.at(i).exactMatch(node->fileName)) + return true; + } + return false; + } +#endif + return true; +} + +QT_END_NAMESPACE + +#include "moc_qfilesystemmodel.cpp" + +#endif // QT_NO_FILESYSTEMMODEL diff --git a/src/widgets/dialogs/qfilesystemmodel.h b/src/widgets/dialogs/qfilesystemmodel.h new file mode 100644 index 0000000000..ce907cd10e --- /dev/null +++ b/src/widgets/dialogs/qfilesystemmodel.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMMODEL_H +#define QFILESYSTEMMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtCore/qpair.h> +#include <QtCore/qdir.h> +#include <QtWidgets/qicon.h> +#include <QtCore/qdiriterator.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILESYSTEMMODEL + +class ExtendedInformation; +class QFileSystemModelPrivate; +class QFileIconProvider; + +class Q_WIDGETS_EXPORT QFileSystemModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool nameFilterDisables READ nameFilterDisables WRITE setNameFilterDisables) + +Q_SIGNALS: + void rootPathChanged(const QString &newPath); + void fileRenamed(const QString &path, const QString &oldName, const QString &newName); + void directoryLoaded(const QString &path); + +public: + enum Roles { + FileIconRole = Qt::DecorationRole, + FilePathRole = Qt::UserRole + 1, + FileNameRole = Qt::UserRole + 2, + FilePermissions = Qt::UserRole + 3 + }; + + explicit QFileSystemModel(QObject *parent = 0); + ~QFileSystemModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex index(const QString &path, int column = 0) const; + QModelIndex parent(const QModelIndex &child) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant myComputer(int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + // QFileSystemModel specific API + QModelIndex setRootPath(const QString &path); + QString rootPath() const; + QDir rootDirectory() const; + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setFilter(QDir::Filters filters); + QDir::Filters filter() const; + + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + + void setReadOnly(bool enable); + bool isReadOnly() const; + + void setNameFilterDisables(bool enable); + bool nameFilterDisables() const; + + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + + QString filePath(const QModelIndex &index) const; + bool isDir(const QModelIndex &index) const; + qint64 size(const QModelIndex &index) const; + QString type(const QModelIndex &index) const; + QDateTime lastModified(const QModelIndex &index) const; + + QModelIndex mkdir(const QModelIndex &parent, const QString &name); + bool rmdir(const QModelIndex &index) const; // ### Qt5: should not be const + inline QString fileName(const QModelIndex &index) const; + inline QIcon fileIcon(const QModelIndex &index) const; + QFile::Permissions permissions(const QModelIndex &index) const; + inline QFileInfo fileInfo(const QModelIndex &index) const; + bool remove(const QModelIndex &index) const; + +protected: + QFileSystemModel(QFileSystemModelPrivate &, QObject *parent = 0); + void timerEvent(QTimerEvent *event); + bool event(QEvent *event); + +private: + Q_DECLARE_PRIVATE(QFileSystemModel) + Q_DISABLE_COPY(QFileSystemModel) + + Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &directory, const QStringList &list)) + Q_PRIVATE_SLOT(d_func(), void _q_performDelayedSort()) + Q_PRIVATE_SLOT(d_func(), void _q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &)) + Q_PRIVATE_SLOT(d_func(), void _q_resolvedName(const QString &fileName, const QString &resolvedName)) + + friend class QFileDialogPrivate; +}; + +inline QString QFileSystemModel::fileName(const QModelIndex &aindex) const +{ return aindex.data(Qt::DisplayRole).toString(); } +inline QIcon QFileSystemModel::fileIcon(const QModelIndex &aindex) const +{ return qvariant_cast<QIcon>(aindex.data(Qt::DecorationRole)); } +inline QFileInfo QFileSystemModel::fileInfo(const QModelIndex &aindex) const +{ return QFileInfo(filePath(aindex)); } + +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILESYSTEMMODEL_H + diff --git a/src/widgets/dialogs/qfilesystemmodel_p.h b/src/widgets/dialogs/qfilesystemmodel_p.h new file mode 100644 index 0000000000..3d5f5b7f00 --- /dev/null +++ b/src/widgets/dialogs/qfilesystemmodel_p.h @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMMODEL_P_H +#define QFILESYSTEMMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfilesystemmodel.h" + +#ifndef QT_NO_FILESYSTEMMODEL + +#include <private/qabstractitemmodel_p.h> +#include <qabstractitemmodel.h> +#include "qfileinfogatherer_p.h" +#include <qpair.h> +#include <qdir.h> +#include <qicon.h> +#include <qdir.h> +#include <qicon.h> +#include <qfileinfo.h> +#include <qtimer.h> +#include <qhash.h> + +QT_BEGIN_NAMESPACE + +class ExtendedInformation; +class QFileSystemModelPrivate; +class QFileIconProvider; + +class Q_AUTOTEST_EXPORT QFileSystemModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QFileSystemModel) + +public: + class QFileSystemNode + { + public: + QFileSystemNode(const QString &filename = QString(), QFileSystemNode *p = 0) + : fileName(filename), populatedChildren(false), isVisible(false), dirtyChildrenIndex(-1), parent(p), info(0) {} + ~QFileSystemNode() { + QHash<QString, QFileSystemNode*>::const_iterator i = children.constBegin(); + while (i != children.constEnd()) { + delete i.value(); + ++i; + } + delete info; + info = 0; + parent = 0; + } + + QString fileName; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QString volumeName; +#endif + + inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; } + inline QString type() const { if (info) return info->displayType; return QLatin1String(""); } + inline QDateTime lastModified() const { if (info) return info->lastModified(); return QDateTime(); } + inline QFile::Permissions permissions() const { if (info) return info->permissions(); return 0; } + inline bool isReadable() const { return ((permissions() & QFile::ReadUser) != 0); } + inline bool isWritable() const { return ((permissions() & QFile::WriteUser) != 0); } + inline bool isExecutable() const { return ((permissions() & QFile::ExeUser) != 0); } + inline bool isDir() const { + if (info) + return info->isDir(); + if (children.count() > 0) + return true; + return false; + } + inline bool isFile() const { if (info) return info->isFile(); return true; } + inline bool isSystem() const { if (info) return info->isSystem(); return true; } + inline bool isHidden() const { if (info) return info->isHidden(); return false; } + inline bool isSymLink() const { if (info) return info->isSymLink(); return false; } + inline bool caseSensitive() const { if (info) return info->isCaseSensitive(); return false; } + inline QIcon icon() const { if (info) return info->icon; return QIcon(); } + + inline bool operator <(const QFileSystemNode &node) const { + if (caseSensitive() || node.caseSensitive()) + return fileName < node.fileName; + return QString::compare(fileName, node.fileName, Qt::CaseInsensitive) < 0; + } + inline bool operator >(const QString &name) const { + if (caseSensitive()) + return fileName > name; + return QString::compare(fileName, name, Qt::CaseInsensitive) > 0; + } + inline bool operator <(const QString &name) const { + if (caseSensitive()) + return fileName < name; + return QString::compare(fileName, name, Qt::CaseInsensitive) < 0; + } + inline bool operator !=(const QExtendedInformation &fileInfo) const { + return !operator==(fileInfo); + } + bool operator ==(const QString &name) const { + if (caseSensitive()) + return fileName == name; + return QString::compare(fileName, name, Qt::CaseInsensitive) == 0; + } + bool operator ==(const QExtendedInformation &fileInfo) const { + return info && (*info == fileInfo); + } + + inline bool hasInformation() const { return info != 0; } + + void populate(const QExtendedInformation &fileInfo) { + if (!info) + info = new QExtendedInformation(fileInfo.fileInfo()); + (*info) = fileInfo; + } + + // children shouldn't normally be accessed directly, use node() + inline int visibleLocation(QString childName) { + return visibleChildren.indexOf(childName); + } + void updateIcon(QFileIconProvider *iconProvider, const QString &path) { + if (info) + info->icon = iconProvider->icon(QFileInfo(path)); + QHash<QString, QFileSystemNode *>::const_iterator iterator; + for(iterator = children.constBegin() ; iterator != children.constEnd() ; ++iterator) { + //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/) + if (!path.isEmpty()) { + if (path.endsWith(QLatin1Char('/'))) + iterator.value()->updateIcon(iconProvider, path + iterator.value()->fileName); + else + iterator.value()->updateIcon(iconProvider, path + QLatin1Char('/') + iterator.value()->fileName); + } else + iterator.value()->updateIcon(iconProvider, iterator.value()->fileName); + } + } + + void retranslateStrings(QFileIconProvider *iconProvider, const QString &path) { + if (info) + info->displayType = iconProvider->type(QFileInfo(path)); + QHash<QString, QFileSystemNode *>::const_iterator iterator; + for(iterator = children.constBegin() ; iterator != children.constEnd() ; ++iterator) { + //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/) + if (!path.isEmpty()) { + if (path.endsWith(QLatin1Char('/'))) + iterator.value()->retranslateStrings(iconProvider, path + iterator.value()->fileName); + else + iterator.value()->retranslateStrings(iconProvider, path + QLatin1Char('/') + iterator.value()->fileName); + } else + iterator.value()->retranslateStrings(iconProvider, iterator.value()->fileName); + } + } + + bool populatedChildren; + bool isVisible; + QHash<QString,QFileSystemNode *> children; + QList<QString> visibleChildren; + int dirtyChildrenIndex; + QFileSystemNode *parent; + + + QExtendedInformation *info; + + }; + + QFileSystemModelPrivate() : + forceSort(true), + sortColumn(0), + sortOrder(Qt::AscendingOrder), + readOnly(true), + setRootPath(false), + filters(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs), + nameFilterDisables(true), // false on windows, true on mac and unix + disableRecursiveSort(false) + { + delayedSortTimer.setSingleShot(true); + } + + void init(); + /* + \internal + + Return true if index which is owned by node is hidden by the filter. + */ + inline bool isHiddenByFilter(QFileSystemNode *indexNode, const QModelIndex &index) const + { + return (indexNode != &root && !index.isValid()); + } + QFileSystemNode *node(const QModelIndex &index) const; + QFileSystemNode *node(const QString &path, bool fetch = true) const; + inline QModelIndex index(const QString &path) { return index(node(path)); } + QModelIndex index(const QFileSystemNode *node) const; + bool filtersAcceptsNode(const QFileSystemNode *node) const; + bool passNameFilters(const QFileSystemNode *node) const; + void removeNode(QFileSystemNode *parentNode, const QString &name); + QFileSystemNode* addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo &info); + void addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles); + void removeVisibleFile(QFileSystemNode *parentNode, int visibleLocation); + void sortChildren(int column, const QModelIndex &parent); + + inline int translateVisibleLocation(QFileSystemNode *parent, int row) const { + if (sortOrder != Qt::AscendingOrder) { + if (parent->dirtyChildrenIndex == -1) + return parent->visibleChildren.count() - row - 1; + + if (row < parent->dirtyChildrenIndex) + return parent->dirtyChildrenIndex - row - 1; + } + + return row; + } + + inline static QString myComputer() { + // ### TODO We should query the system to find out what the string should be + // XP == "My Computer", + // Vista == "Computer", + // OS X == "Computer" (sometime user generated) "Benjamin's PowerBook G4" +#ifdef Q_OS_WIN + return QFileSystemModel::tr("My Computer"); +#else + return QFileSystemModel::tr("Computer"); +#endif + } + + inline void delayedSort() { + if (!delayedSortTimer.isActive()) + delayedSortTimer.start(0); + } + + static bool caseInsensitiveLessThan(const QString &s1, const QString &s2) + { + return QString::compare(s1, s2, Qt::CaseInsensitive) < 0; + } + + static bool nodeCaseInsensitiveLessThan(const QFileSystemModelPrivate::QFileSystemNode &s1, const QFileSystemModelPrivate::QFileSystemNode &s2) + { + return QString::compare(s1.fileName, s2.fileName, Qt::CaseInsensitive) < 0; + } + + QIcon icon(const QModelIndex &index) const; + QString name(const QModelIndex &index) const; + QString displayName(const QModelIndex &index) const; + QString filePath(const QModelIndex &index) const; + QString size(const QModelIndex &index) const; + static QString size(qint64 bytes); + QString type(const QModelIndex &index) const; + QString time(const QModelIndex &index) const; + + void _q_directoryChanged(const QString &directory, const QStringList &list); + void _q_performDelayedSort(); + void _q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo> > &); + void _q_resolvedName(const QString &fileName, const QString &resolvedName); + + static int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); + + QDir rootDir; +#ifndef QT_NO_FILESYSTEMWATCHER + QFileInfoGatherer fileInfoGatherer; +#endif + QTimer delayedSortTimer; + bool forceSort; + int sortColumn; + Qt::SortOrder sortOrder; + bool readOnly; + bool setRootPath; + QDir::Filters filters; + QHash<const QFileSystemNode*, bool> bypassFilters; + bool nameFilterDisables; + //This flag is an optimization for the QFileDialog + //It enable a sort which is not recursive, it means + //we sort only what we see. + bool disableRecursiveSort; +#ifndef QT_NO_REGEXP + QList<QRegExp> nameFilters; +#endif + // ### Qt 5: resolvedSymLinks goes away + QHash<QString, QString> resolvedSymLinks; + + QFileSystemNode root; + + QBasicTimer fetchingTimer; + struct Fetching { + QString dir; + QString file; + const QFileSystemNode *node; + }; + QList<Fetching> toFetch; + +}; +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE + +#endif + diff --git a/src/widgets/dialogs/qfontdialog.cpp b/src/widgets/dialogs/qfontdialog.cpp new file mode 100644 index 0000000000..df49fee9bb --- /dev/null +++ b/src/widgets/dialogs/qfontdialog.cpp @@ -0,0 +1,1077 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowdefs.h" + +#ifndef QT_NO_FONTDIALOG + +#include "qfontdialog.h" +#include "qfontdialog_p.h" + +#include <qapplication.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qevent.h> +#include <qfontdatabase.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qpushbutton.h> +#include <qstyle.h> +#include <qdialogbuttonbox.h> +#include <qheaderview.h> +#include <qlistview.h> +#include <qstringlistmodel.h> +#include <qvalidator.h> +#include <private/qdialog_p.h> +#include <private/qfont_p.h> + +#if defined(Q_WS_S60) +#include <QtWidgets/qdesktopwidget.h> +#endif + +QT_BEGIN_NAMESPACE + +class QFontListView : public QListView +{ + Q_OBJECT +public: + QFontListView(QWidget *parent); + inline QStringListModel *model() const { + return static_cast<QStringListModel *>(QListView::model()); + } + inline void setCurrentItem(int item) { + QListView::setCurrentIndex(static_cast<QAbstractListModel*>(model())->index(item)); + } + inline int currentItem() const { + return QListView::currentIndex().row(); + } + inline int count() const { + return model()->rowCount(); + } + inline QString currentText() const { + int row = QListView::currentIndex().row(); + return row < 0 ? QString() : model()->stringList().at(row); + } + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QListView::currentChanged(current, previous); + if (current.isValid()) + emit highlighted(current.row()); + } + QString text(int i) const { + return model()->stringList().at(i); + } +signals: + void highlighted(int); +}; + +QFontListView::QFontListView(QWidget *parent) + : QListView(parent) +{ + setModel(new QStringListModel(parent)); + setEditTriggers(NoEditTriggers); +} + +static const Qt::WindowFlags DefaultWindowFlags = + Qt::Dialog | Qt::WindowSystemMenuHint; + +/*! + \class QFontDialog + \ingroup standard-dialogs + + \brief The QFontDialog class provides a dialog widget for selecting a font. + + A font dialog is created through one of the static getFont() + functions. + + Examples: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 0 + + The dialog can also be used to set a widget's font directly: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 1 + If the user clicks OK the font they chose will be used for myWidget, + and if they click Cancel the original font is used. + + \image plastique-fontdialog.png A font dialog in the Plastique widget style. + + \sa QFont, QFontInfo, QFontMetrics, QColorDialog, QFileDialog, QPrintDialog, + {Standard Dialogs Example} +*/ + +/*! + \since 4.5 + + Constructs a standard font dialog. + + Use setCurrentFont() to set the initial font attributes. + + The \a parent parameter is passed to the QDialog constructor. + + \sa getFont() +*/ +QFontDialog::QFontDialog(QWidget *parent) + : QDialog(*new QFontDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QFontDialog); + d->init(); +} + +/*! + \since 4.5 + + Constructs a standard font dialog with the given \a parent and specified + \a initial color. +*/ +QFontDialog::QFontDialog(const QFont &initial, QWidget *parent) + : QDialog(*new QFontDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QFontDialog); + d->init(); + setCurrentFont(initial); +} + +void QFontDialogPrivate::init() +{ + Q_Q(QFontDialog); + +#ifdef Q_WS_MAC + nativeDialogInUse = false; + delegate = 0; +#endif + + q->setSizeGripEnabled(true); + q->setWindowTitle(QFontDialog::tr("Select Font")); + + // grid + familyEdit = new QLineEdit(q); + familyEdit->setReadOnly(true); + familyList = new QFontListView(q); + familyEdit->setFocusProxy(familyList); + + familyAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + familyAccel->setBuddy(familyList); +#endif + familyAccel->setIndent(2); + + styleEdit = new QLineEdit(q); + styleEdit->setReadOnly(true); + styleList = new QFontListView(q); + styleEdit->setFocusProxy(styleList); + + styleAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + styleAccel->setBuddy(styleList); +#endif + styleAccel->setIndent(2); + + sizeEdit = new QLineEdit(q); + sizeEdit->setFocusPolicy(Qt::ClickFocus); + QIntValidator *validator = new QIntValidator(1, 512, q); + sizeEdit->setValidator(validator); + sizeList = new QFontListView(q); + + sizeAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + sizeAccel->setBuddy(sizeEdit); +#endif + sizeAccel->setIndent(2); + + // effects box + effects = new QGroupBox(q); + QVBoxLayout *vbox = new QVBoxLayout(effects); + strikeout = new QCheckBox(effects); + vbox->addWidget(strikeout); + underline = new QCheckBox(effects); + vbox->addWidget(underline); + + sample = new QGroupBox(q); + QHBoxLayout *hbox = new QHBoxLayout(sample); + sampleEdit = new QLineEdit(sample); + sampleEdit->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); + sampleEdit->setAlignment(Qt::AlignCenter); + // Note that the sample text is *not* translated with tr(), as the + // characters used depend on the charset encoding. + sampleEdit->setText(QLatin1String("AaBbYyZz")); + hbox->addWidget(sampleEdit); + + writingSystemCombo = new QComboBox(q); + + writingSystemAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + writingSystemAccel->setBuddy(writingSystemCombo); +#endif + writingSystemAccel->setIndent(2); + + size = 0; + smoothScalable = false; + + QObject::connect(writingSystemCombo, SIGNAL(activated(int)), q, SLOT(_q_writingSystemHighlighted(int))); + QObject::connect(familyList, SIGNAL(highlighted(int)), q, SLOT(_q_familyHighlighted(int))); + QObject::connect(styleList, SIGNAL(highlighted(int)), q, SLOT(_q_styleHighlighted(int))); + QObject::connect(sizeList, SIGNAL(highlighted(int)), q, SLOT(_q_sizeHighlighted(int))); + QObject::connect(sizeEdit, SIGNAL(textChanged(QString)), q, SLOT(_q_sizeChanged(QString))); + + QObject::connect(strikeout, SIGNAL(clicked()), q, SLOT(_q_updateSample())); + QObject::connect(underline, SIGNAL(clicked()), q, SLOT(_q_updateSample())); + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + QFontDatabase::WritingSystem ws = QFontDatabase::WritingSystem(i); + QString writingSystemName = QFontDatabase::writingSystemName(ws); + if (writingSystemName.isEmpty()) + break; + writingSystemCombo->addItem(writingSystemName); + } + + updateFamilies(); + if (familyList->count() != 0) + familyList->setCurrentItem(0); + + // grid layout + QGridLayout *mainGrid = new QGridLayout(q); + + int spacing = mainGrid->spacing(); + if (spacing >= 0) { // uniform spacing + mainGrid->setSpacing(0); + + mainGrid->setColumnMinimumWidth(1, spacing); + mainGrid->setColumnMinimumWidth(3, spacing); + + int margin = 0; + mainGrid->getContentsMargins(0, 0, 0, &margin); + + mainGrid->setRowMinimumHeight(3, margin); + mainGrid->setRowMinimumHeight(6, 2); + mainGrid->setRowMinimumHeight(8, margin); + } + + mainGrid->addWidget(familyAccel, 0, 0); + mainGrid->addWidget(familyEdit, 1, 0); + mainGrid->addWidget(familyList, 2, 0); + + mainGrid->addWidget(styleAccel, 0, 2); + mainGrid->addWidget(styleEdit, 1, 2); + mainGrid->addWidget(styleList, 2, 2); + + mainGrid->addWidget(sizeAccel, 0, 4); + mainGrid->addWidget(sizeEdit, 1, 4); + mainGrid->addWidget(sizeList, 2, 4); + + mainGrid->setColumnStretch(0, 38); + mainGrid->setColumnStretch(2, 24); + mainGrid->setColumnStretch(4, 10); + + mainGrid->addWidget(effects, 4, 0); + + mainGrid->addWidget(sample, 4, 2, 4, 3); + + mainGrid->addWidget(writingSystemAccel, 5, 0); + mainGrid->addWidget(writingSystemCombo, 7, 0); + + buttonBox = new QDialogButtonBox(q); + mainGrid->addWidget(buttonBox, 9, 0, 1, 5); + + QPushButton *button + = static_cast<QPushButton *>(buttonBox->addButton(QDialogButtonBox::Ok)); + QObject::connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + button->setDefault(true); + + buttonBox->addButton(QDialogButtonBox::Cancel); + QObject::connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + +#if defined(Q_WS_WINCE) + q->resize(180, 120); +#elif defined(Q_WS_S60) + q->resize(QApplication::desktop()->availableGeometry(QCursor::pos()).size()); +#else + q->resize(500, 360); +#endif // Q_WS_WINCE + + sizeEdit->installEventFilter(q); + familyList->installEventFilter(q); + styleList->installEventFilter(q); + sizeList->installEventFilter(q); + + familyList->setFocus(); + retranslateStrings(); +} + +/*! + \internal + Destroys the font dialog and frees up its storage. +*/ + +QFontDialog::~QFontDialog() +{ +#ifdef Q_WS_MAC + Q_D(QFontDialog); + if (d->delegate) { + d->closeCocoaFontPanel(); + return; + } +#endif +} + +/*! + Executes a modal font dialog and returns a font. + + If the user clicks \gui OK, the selected font is returned. If the user + clicks \gui Cancel, the \a initial font is returned. + + The dialog is constructed with the given \a parent and the options specified + in \a options. \a title is shown as the window title of the dialog and \a + initial is the initially selected font. If the \a ok parameter is not-null, + the value it refers to is set to true if the user clicks \gui OK, and set to + false if the user clicks \gui Cancel. + + Examples: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 2 + + The dialog can also be used to set a widget's font directly: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 3 + In this example, if the user clicks OK the font they chose will be + used, and if they click Cancel the original font is used. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title, + FontDialogOptions options) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, title, options); +} + +/*! + \overload + \since 4.5 +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, title, 0); +} + +/*! + \overload +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, QString(), 0); +} + +/*! + \overload + + Executes a modal font dialog and returns a font. + + If the user clicks \gui OK, the selected font is returned. If the user + clicks \gui Cancel, the Qt default font is returned. + + The dialog is constructed with the given \a parent. + If the \a ok parameter is not-null, the value it refers to is set + to true if the user clicks \gui OK, and false if the user clicks + \gui Cancel. + + Example: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 4 + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. +*/ +QFont QFontDialog::getFont(bool *ok, QWidget *parent) +{ + QFont initial; + return QFontDialogPrivate::getFont(ok, initial, parent, QString(), 0); +} + +QFont QFontDialogPrivate::getFont(bool *ok, const QFont &initial, QWidget *parent, + const QString &title, QFontDialog::FontDialogOptions options) +{ + QFontDialog dlg(parent); + dlg.setOptions(options); + dlg.setCurrentFont(initial); + if (!title.isEmpty()) + dlg.setWindowTitle(title); + + int ret = (dlg.exec() || (options & QFontDialog::NoButtons)); + if (ok) + *ok = !!ret; + if (ret) { + return dlg.selectedFont(); + } else { + return initial; + } +} + +/*! + \internal + An event filter to make the Up, Down, PageUp and PageDown keys work + correctly in the line edits. The source of the event is the object + \a o and the event is \a e. +*/ + +bool QFontDialog::eventFilter(QObject *o , QEvent *e) +{ + Q_D(QFontDialog); + if (e->type() == QEvent::KeyPress) { + QKeyEvent *k = (QKeyEvent *)e; + if (o == d->sizeEdit && + (k->key() == Qt::Key_Up || + k->key() == Qt::Key_Down || + k->key() == Qt::Key_PageUp || + k->key() == Qt::Key_PageDown)) { + + int ci = d->sizeList->currentItem(); + (void)QApplication::sendEvent(d->sizeList, k); + + if (ci != d->sizeList->currentItem() + && style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, this)) + d->sizeEdit->selectAll(); + return true; + } else if ((o == d->familyList || o == d->styleList) && + (k->key() == Qt::Key_Return || k->key() == Qt::Key_Enter)) { + k->accept(); + accept(); + return true; + } + } else if (e->type() == QEvent::FocusIn + && style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, this)) { + if (o == d->familyList) + d->familyEdit->selectAll(); + else if (o == d->styleList) + d->styleEdit->selectAll(); + else if (o == d->sizeList) + d->sizeEdit->selectAll(); + } else if (e->type() == QEvent::MouseButtonPress && o == d->sizeList) { + d->sizeEdit->setFocus(); + } + return QDialog::eventFilter(o, e); +} + +/* + Updates the contents of the "font family" list box. This + function can be reimplemented if you have special requirements. +*/ + +void QFontDialogPrivate::updateFamilies() +{ + Q_Q(QFontDialog); + + enum match_t { MATCH_NONE = 0, MATCH_LAST_RESORT = 1, MATCH_APP = 2, MATCH_FAMILY = 3 }; + + QStringList familyNames = fdb.families(writingSystem); + + familyList->model()->setStringList(familyNames); + + QString foundryName1, familyName1, foundryName2, familyName2; + int bestFamilyMatch = -1; + match_t bestFamilyType = MATCH_NONE; + + QFont f; + + // ##### do the right thing for a list of family names in the font. + QFontDatabase::parseFontName(family, foundryName1, familyName1); + + QStringList::const_iterator it = familyNames.constBegin(); + int i = 0; + for(; it != familyNames.constEnd(); ++it, ++i) { + QFontDatabase::parseFontName(*it, foundryName2, familyName2); + + //try to match... + if (familyName1 == familyName2) { + bestFamilyType = MATCH_FAMILY; + if (foundryName1 == foundryName2) { + bestFamilyMatch = i; + break; + } + if (bestFamilyMatch < MATCH_FAMILY) + bestFamilyMatch = i; + } + + //and try some fall backs + match_t type = MATCH_NONE; + if (bestFamilyType <= MATCH_NONE && familyName2 == f.lastResortFamily()) + type = MATCH_LAST_RESORT; + if (bestFamilyType <= MATCH_LAST_RESORT && familyName2 == f.family()) + type = MATCH_APP; + // ### add fallback for writingSystem + if (type != MATCH_NONE) { + bestFamilyType = type; + bestFamilyMatch = i; + } + } + + if (i != -1 && bestFamilyType != MATCH_NONE) + familyList->setCurrentItem(bestFamilyMatch); + else + familyList->setCurrentItem(0); + familyEdit->setText(familyList->currentText()); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && familyList->hasFocus()) + familyEdit->selectAll(); + + updateStyles(); +} + +/* + Updates the contents of the "font style" list box. This + function can be reimplemented if you have special requirements. +*/ +void QFontDialogPrivate::updateStyles() +{ + Q_Q(QFontDialog); + QStringList styles = fdb.styles(familyList->currentText()); + styleList->model()->setStringList(styles); + + if (styles.isEmpty()) { + styleEdit->clear(); + smoothScalable = false; + } else { + if (!style.isEmpty()) { + bool found = false; + bool first = true; + QString cstyle = style; + + redo: + for (int i = 0; i < (int)styleList->count(); i++) { + if (cstyle == styleList->text(i)) { + styleList->setCurrentItem(i); + found = true; + break; + } + } + if (!found && first) { + if (cstyle.contains(QLatin1String("Italic"))) { + cstyle.replace(QLatin1String("Italic"), QLatin1String("Oblique")); + first = false; + goto redo; + } else if (cstyle.contains(QLatin1String("Oblique"))) { + cstyle.replace(QLatin1String("Oblique"), QLatin1String("Italic")); + first = false; + goto redo; + } + } + if (!found) + styleList->setCurrentItem(0); + } else { + styleList->setCurrentItem(0); + } + + styleEdit->setText(styleList->currentText()); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && styleList->hasFocus()) + styleEdit->selectAll(); + + smoothScalable = fdb.isSmoothlyScalable(familyList->currentText(), styleList->currentText()); + } + + updateSizes(); +} + +/*! + \internal + Updates the contents of the "font size" list box. This + function can be reimplemented if you have special requirements. +*/ + +void QFontDialogPrivate::updateSizes() +{ + Q_Q(QFontDialog); + + if (!familyList->currentText().isEmpty()) { + QList<int> sizes = fdb.pointSizes(familyList->currentText(), styleList->currentText()); + + int i = 0; + int current = -1; + QStringList str_sizes; + for(QList<int>::const_iterator it = sizes.constBegin(); it != sizes.constEnd(); ++it) { + str_sizes.append(QString::number(*it)); + if (current == -1 && *it >= size) + current = i; + ++i; + } + sizeList->model()->setStringList(str_sizes); + if (current == -1) { + // we request a size bigger than the ones in the list, select the biggest one + current = sizeList->count() - 1; + } + sizeList->setCurrentItem(current); + + sizeEdit->blockSignals(true); + sizeEdit->setText((smoothScalable ? QString::number(size) : sizeList->currentText())); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && sizeList->hasFocus()) + sizeEdit->selectAll(); + sizeEdit->blockSignals(false); + } else { + sizeEdit->clear(); + } + + _q_updateSample(); +} + +void QFontDialogPrivate::_q_updateSample() +{ + // compute new font + int pSize = sizeEdit->text().toInt(); + QFont newFont(fdb.font(familyList->currentText(), style, pSize)); + newFont.setStrikeOut(strikeout->isChecked()); + newFont.setUnderline(underline->isChecked()); + + if (familyList->currentText().isEmpty()) + sampleEdit->clear(); + + updateSampleFont(newFont); +} + +void QFontDialogPrivate::updateSampleFont(const QFont &newFont) +{ + Q_Q(QFontDialog); + if (newFont != sampleEdit->font()) { + sampleEdit->setFont(newFont); + emit q->currentFontChanged(newFont); + } +} + +/*! + \internal +*/ +void QFontDialogPrivate::_q_writingSystemHighlighted(int index) +{ + writingSystem = QFontDatabase::WritingSystem(index); + sampleEdit->setText(fdb.writingSystemSample(writingSystem)); + updateFamilies(); +} + +/*! + \internal +*/ +void QFontDialogPrivate::_q_familyHighlighted(int i) +{ + Q_Q(QFontDialog); + family = familyList->text(i); + familyEdit->setText(family); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && familyList->hasFocus()) + familyEdit->selectAll(); + + updateStyles(); +} + + +/*! + \internal +*/ + +void QFontDialogPrivate::_q_styleHighlighted(int index) +{ + Q_Q(QFontDialog); + QString s = styleList->text(index); + styleEdit->setText(s); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && styleList->hasFocus()) + styleEdit->selectAll(); + + style = s; + + updateSizes(); +} + + +/*! + \internal +*/ + +void QFontDialogPrivate::_q_sizeHighlighted(int index) +{ + Q_Q(QFontDialog); + QString s = sizeList->text(index); + sizeEdit->setText(s); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && sizeEdit->hasFocus()) + sizeEdit->selectAll(); + + size = s.toInt(); + _q_updateSample(); +} + +/*! + \internal + This slot is called if the user changes the font size. + The size is passed in the \a s argument as a \e string. +*/ + +void QFontDialogPrivate::_q_sizeChanged(const QString &s) +{ + // no need to check if the conversion is valid, since we have an QIntValidator in the size edit + int size = s.toInt(); + if (this->size == size) + return; + + this->size = size; + if (sizeList->count() != 0) { + int i; + for (i = 0; i < sizeList->count() - 1; i++) { + if (sizeList->text(i).toInt() >= this->size) + break; + } + sizeList->blockSignals(true); + sizeList->setCurrentItem(i); + sizeList->blockSignals(false); + } + _q_updateSample(); +} + +void QFontDialogPrivate::retranslateStrings() +{ + familyAccel->setText(QFontDialog::tr("&Font")); + styleAccel->setText(QFontDialog::tr("Font st&yle")); + sizeAccel->setText(QFontDialog::tr("&Size")); +#ifndef Q_WS_S60 + // Removed the title due to lack of screen estate in small S60 screen. + // The effects are descriptive without a title (strikeout, underline). + effects->setTitle(QFontDialog::tr("Effects")); +#endif + strikeout->setText(QFontDialog::tr("Stri&keout")); + underline->setText(QFontDialog::tr("&Underline")); + sample->setTitle(QFontDialog::tr("Sample")); + writingSystemAccel->setText(QFontDialog::tr("Wr&iting System")); +} + +/*! + \reimp +*/ +void QFontDialog::changeEvent(QEvent *e) +{ + Q_D(QFontDialog); + if (e->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +/*! + \since 4.5 + + \property QFontDialog::currentFont + \brief the current font of the dialog. +*/ + +/*! + \since 4.5 + + Sets the font highlighted in the QFontDialog to the given \a font. + + \sa selectedFont() +*/ +void QFontDialog::setCurrentFont(const QFont &font) +{ + Q_D(QFontDialog); + d->family = font.family(); + d->style = d->fdb.styleString(font); + d->size = font.pointSize(); + if (d->size == -1) { + QFontInfo fi(font); + d->size = fi.pointSize(); + } + d->strikeout->setChecked(font.strikeOut()); + d->underline->setChecked(font.underline()); + d->updateFamilies(); + +#ifdef Q_WS_MAC + if (d->delegate) + QFontDialogPrivate::setFont(d->delegate, font); +#endif +} + +/*! + \since 4.5 + + Returns the current font. + + \sa selectedFont() +*/ +QFont QFontDialog::currentFont() const +{ + Q_D(const QFontDialog); + return d->sampleEdit->font(); +} + +/*! + Returns the font that the user selected by clicking the \gui{OK} + or equivalent button. + + \note This font is not always the same as the font held by the + \l currentFont property since the user can choose different fonts + before finally selecting the one to use. +*/ +QFont QFontDialog::selectedFont() const +{ + Q_D(const QFontDialog); + return d->selectedFont; +} + +/*! + \enum QFontDialog::FontDialogOption + \since 4.5 + + This enum specifies various options that affect the look and feel + of a font dialog. + + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value DontUseNativeDialog Use Qt's standard font dialog on the Mac instead of Apple's + native font panel. (Currently, the native dialog is never used, + but this is likely to change in future Qt releases.) + + \sa options, setOption(), testOption() +*/ + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QFontDialog::setOption(FontDialogOption option, bool on) +{ + Q_D(QFontDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QFontDialog::testOption(FontDialogOption option) const +{ + Q_D(const QFontDialog); + return (d->opts & option) != 0; +} + +/*! + \property QFontDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QFontDialog::setOptions(FontDialogOptions options) +{ + Q_D(QFontDialog); + + FontDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->buttonBox->setVisible(!(options & NoButtons)); +} + +QFontDialog::FontDialogOptions QFontDialog::options() const +{ + Q_D(const QFontDialog); + return d->opts; +} + +#ifdef Q_WS_MAC +// can only have one Cocoa font panel active +bool QFontDialogPrivate::sharedFontPanelAvailable = true; +#endif + +/*! + \since 4.5 + \overload + + Opens the dialog and connects its fontSelected() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QFontDialog::open(QObject *receiver, const char *member) +{ + Q_D(QFontDialog); + connect(this, SIGNAL(fontSelected(QFont)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \since 4.5 + + \fn void QFontDialog::currentFontChanged(const QFont &font) + + This signal is emitted when the current font is changed. The new font is + specified in \a font. + + The signal is emitted while a user is selecting a font. Ultimately, the + chosen font may differ from the font currently selected. + + \sa currentFont, fontSelected(), selectedFont() +*/ + +/*! + \since 4.5 + + \fn void QFontDialog::fontSelected(const QFont &font) + + This signal is emitted when a font has been selected. The selected font is + specified in \a font. + + The signal is only emitted when a user has chosen the final font to be + used. It is not emitted while the user is changing the current font in the + font dialog. + + \sa selectedFont(), currentFontChanged(), currentFont +*/ + +/*! + \reimp +*/ +void QFontDialog::setVisible(bool visible) +{ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) != visible) + return; +#ifdef Q_WS_MAC + Q_D(QFontDialog); + if (d->canBeNativeDialog()){ + if (d->setVisible_sys(visible)){ + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen, true); + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); + } + } +#endif // Q_WS_MAC + QDialog::setVisible(visible); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QFontDialog::done(int result) +{ + Q_D(QFontDialog); + QDialog::done(result); + if (result == Accepted) { + // We check if this is the same font we had before, if so we emit currentFontChanged + QFont selectedFont = currentFont(); + if(selectedFont != d->selectedFont) + emit(currentFontChanged(selectedFont)); + d->selectedFont = selectedFont; + emit fontSelected(d->selectedFont); + } else + d->selectedFont = QFont(); + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(fontSelected(QFont)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +#ifdef Q_WS_MAC +bool QFontDialogPrivate::canBeNativeDialog() +{ + Q_Q(QFontDialog); + if (nativeDialogInUse) + return true; + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return false; + if (opts & QFontDialog::DontUseNativeDialog) + return false; + + QLatin1String staticName(QFontDialog::staticMetaObject.className()); + QLatin1String dynamicName(q->metaObject()->className()); + return (staticName == dynamicName); +} +#endif // Q_WS_MAC + +/*! + \fn QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget* parent, const char* name) + \since 4.5 + + Call getFont(\a ok, \a initial, \a parent) instead. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. + + The \a name parameter is ignored. +*/ + +/*! + \fn QFont QFontDialog::getFont(bool *ok, QWidget* parent, const char* name) + + Call getFont(\a ok, \a parent) instead. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. + + The \a name parameter is ignored. +*/ + +QT_END_NAMESPACE + +#include "qfontdialog.moc" +#include "moc_qfontdialog.cpp" + +#endif // QT_NO_FONTDIALOG diff --git a/src/widgets/dialogs/qfontdialog.h b/src/widgets/dialogs/qfontdialog.h new file mode 100644 index 0000000000..03ef2b7e81 --- /dev/null +++ b/src/widgets/dialogs/qfontdialog.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDIALOG_H +#define QFONTDIALOG_H + +#include <QtGui/qwindowdefs.h> +#include <QtWidgets/qdialog.h> +#include <QtGui/qfont.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FONTDIALOG + +class QFontDialogPrivate; + +class Q_WIDGETS_EXPORT QFontDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QFontDialog) + Q_ENUMS(FontDialogOption) + Q_PROPERTY(QFont currentFont READ currentFont WRITE setCurrentFont NOTIFY currentFontChanged) + Q_PROPERTY(FontDialogOptions options READ options WRITE setOptions) + +public: + enum FontDialogOption { + NoButtons = 0x00000001, + DontUseNativeDialog = 0x00000002 + }; + + Q_DECLARE_FLAGS(FontDialogOptions, FontDialogOption) + + explicit QFontDialog(QWidget *parent = 0); + explicit QFontDialog(const QFont &initial, QWidget *parent = 0); + ~QFontDialog(); + + void setCurrentFont(const QFont &font); + QFont currentFont() const; + + QFont selectedFont() const; + + void setOption(FontDialogOption option, bool on = true); + bool testOption(FontDialogOption option) const; + void setOptions(FontDialogOptions options); + FontDialogOptions options() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + void setVisible(bool visible); + + // ### Qt 5: merge overloads + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title, + FontDialogOptions options); + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title); + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent = 0); + static QFont getFont(bool *ok, QWidget *parent = 0); + +#ifdef QT3_SUPPORT + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const char *name) + { Q_UNUSED(name); return getFont(ok, initial, parent); } + static QFont getFont(bool *ok, QWidget *parent, const char *name) + { Q_UNUSED(name); return getFont(ok, parent); } +#endif + +Q_SIGNALS: + void currentFontChanged(const QFont &font); + void fontSelected(const QFont &font); + +protected: + void changeEvent(QEvent *event); + void done(int result); + +private: + // ### Qt 5: make protected + bool eventFilter(QObject *object, QEvent *event); + + Q_DISABLE_COPY(QFontDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_sizeChanged(const QString &)) + Q_PRIVATE_SLOT(d_func(), void _q_familyHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_writingSystemHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_styleHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_sizeHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSample()) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFontDialog::FontDialogOptions) + +#endif // QT_NO_FONTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTDIALOG_H diff --git a/src/widgets/dialogs/qfontdialog_mac.mm b/src/widgets/dialogs/qfontdialog_mac.mm new file mode 100644 index 0000000000..088aa52085 --- /dev/null +++ b/src/widgets/dialogs/qfontdialog_mac.mm @@ -0,0 +1,699 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontdialog_p.h" +#if !defined(QT_NO_FONTDIALOG) && defined(Q_WS_MAC) +#include <qapplication.h> +#include <qdialogbuttonbox.h> +#include <qlineedit.h> +#include <private/qapplication_p.h> +#include <private/qfont_p.h> +#include <private/qfontengine_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qt_mac_p.h> +#include <qabstracteventdispatcher.h> +#include <qdebug.h> +#include <private/qfontengine_coretext_p.h> +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#if !CGFLOAT_DEFINED +typedef float CGFloat; // Should only not be defined on 32-bit platforms +#endif + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptNSPanelCtor(); +extern void macStopInterceptNSPanelCtor(); +extern NSButton *macCreateButton(const char *text, NSView *superview); +extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm + +QT_END_NAMESPACE +QT_USE_NAMESPACE + +// should a priori be kept in sync with qcolordialog_mac.mm +const CGFloat ButtonMinWidth = 78.0; +const CGFloat ButtonMinHeight = 32.0; +const CGFloat ButtonSpacing = 0.0; +const CGFloat ButtonTopMargin = 0.0; +const CGFloat ButtonBottomMargin = 7.0; +const CGFloat ButtonSideMargin = 9.0; + +// looks better with some margins +const CGFloat DialogTopMargin = 7.0; +const CGFloat DialogSideMargin = 9.0; + +const int StyleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask; + +@class QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate); + + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + +@protocol NSWindowDelegate <NSObject> +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +@end + +#endif + +@interface QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) : NSObject <NSWindowDelegate> { + NSFontPanel *mFontPanel; + NSView *mStolenContentView; + NSButton *mOkButton; + NSButton *mCancelButton; + QFontDialogPrivate *mPriv; + QFont *mQtFont; + BOOL mPanelHackedWithButtons; + CGFloat mDialogExtraWidth; + CGFloat mDialogExtraHeight; + int mReturnCode; + BOOL mAppModal; +} +- (id)initWithFontPanel:(NSFontPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QFontDialogPrivate *)priv + extraWidth:(CGFloat)extraWidth + extraHeight:(CGFloat)extraHeight; +- (void)showModelessPanel; +- (void)showWindowModalSheet:(QWidget *)docWidget; +- (void)runApplicationModalPanel; +- (BOOL)isAppModal; +- (void)changeFont:(id)sender; +- (void)changeAttributes:(id)sender; +- (BOOL)windowShouldClose:(id)window; +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +- (void)relayout; +- (void)relayoutToContentSize:(NSSize)frameSize; +- (void)onOkClicked; +- (void)onCancelClicked; +- (NSFontPanel *)fontPanel; +- (NSWindow *)actualPanel; +- (NSSize)dialogExtraSize; +- (void)setQtFont:(const QFont &)newFont; +- (QFont)qtFont; +- (void)finishOffWithCode:(NSInteger)result; +- (void)cleanUpAfterMyself; +- (void)setSubwindowStacking; +@end + +static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont) +{ + QFont newFont; + if (cocoaFont) { + int pSize = qRound([cocoaFont pointSize]); + QString family(qt_mac_NSStringToQString([cocoaFont familyName])); + QString typeface(qt_mac_NSStringToQString([cocoaFont fontName])); + + int hyphenPos = typeface.indexOf(QLatin1Char('-')); + if (hyphenPos != -1) { + typeface.remove(0, hyphenPos + 1); + } else { + typeface = QLatin1String("Normal"); + } + + newFont = QFontDatabase().font(family, typeface, pSize); + newFont.setUnderline(resolveFont.underline()); + newFont.setStrikeOut(resolveFont.strikeOut()); + + } + return newFont; +} + +@implementation QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) +- (id)initWithFontPanel:(NSFontPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QFontDialogPrivate *)priv + extraWidth:(CGFloat)extraWidth + extraHeight:(CGFloat)extraHeight +{ + self = [super init]; + mFontPanel = panel; + mStolenContentView = stolenContentView; + mOkButton = okButton; + mCancelButton = cancelButton; + mPriv = priv; + mPanelHackedWithButtons = (okButton != 0); + mDialogExtraWidth = extraWidth; + mDialogExtraHeight = extraHeight; + mReturnCode = -1; + mAppModal = false; + + if (mPanelHackedWithButtons) { + [self relayout]; + + [okButton setAction:@selector(onOkClicked)]; + [okButton setTarget:self]; + + [cancelButton setAction:@selector(onCancelClicked)]; + [cancelButton setTarget:self]; + } + + mQtFont = new QFont(); + return self; +} + +- (void)setSubwindowStacking +{ +#ifdef QT_MAC_USE_COCOA + // Stack the native dialog in front of its parent, if any: + QFontDialog *q = mPriv->fontDialog(); + if (!qt_mac_is_macsheet(q)) { + if (QWidget *parent = q->parentWidget()) { + if (parent->isWindow()) { + [qt_mac_window_for(parent) + addChildWindow:[mStolenContentView window] ordered:NSWindowAbove]; + } + } + } +#endif +} + +- (void)dealloc +{ + delete mQtFont; + [super dealloc]; +} + +- (void)showModelessPanel +{ + mAppModal = false; + NSWindow *ourPanel = [mStolenContentView window]; + [ourPanel makeKeyAndOrderFront:self]; +} + +- (void)runApplicationModalPanel +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); + mAppModal = true; + NSWindow *ourPanel = [mStolenContentView window]; + [ourPanel setReleasedWhenClosed:NO]; + [NSApp runModalForWindow:ourPanel]; + QAbstractEventDispatcher::instance()->interrupt(); + + if (mReturnCode == NSOKButton) + mPriv->fontDialog()->accept(); + else + mPriv->fontDialog()->reject(); +} + +- (BOOL)isAppModal +{ + return mAppModal; +} + +- (void)showWindowModalSheet:(QWidget *)docWidget +{ +#ifdef QT_MAC_USE_COCOA + NSWindow *window = qt_mac_window_for(docWidget); +#else + WindowRef hiwindowRef = qt_mac_window_for(docWidget); + NSWindow *window = [[NSWindow alloc] initWithWindowRef:hiwindowRef]; + CFRetain(hiwindowRef); +#endif + + mAppModal = false; + NSWindow *ourPanel = [mStolenContentView window]; + [NSApp beginSheet:ourPanel + modalForWindow:window + modalDelegate:0 + didEndSelector:0 + contextInfo:0 ]; + +#ifndef QT_MAC_USE_COCOA + CFRelease(hiwindowRef); +#endif +} + +- (void)changeFont:(id)sender +{ + NSFont *dummyFont = [NSFont userFontOfSize:12.0]; + [self setQtFont:qfontForCocoaFont([sender convertFont:dummyFont], *mQtFont)]; + if (mPriv) + mPriv->updateSampleFont(*mQtFont); +} + +- (void)changeAttributes:(id)sender +{ + NSDictionary *dummyAttribs = [NSDictionary dictionary]; + NSDictionary *attribs = [sender convertAttributes:dummyAttribs]; + +#ifdef QT_MAC_USE_COCOA + for (id key in attribs) { +#else + NSEnumerator *enumerator = [attribs keyEnumerator]; + id key; + while((key = [enumerator nextObject])) { +#endif + NSNumber *number = static_cast<NSNumber *>([attribs objectForKey:key]); + if ([key isEqual:NSUnderlineStyleAttributeName]) { + mQtFont->setUnderline([number intValue] != NSUnderlineStyleNone); + } else if ([key isEqual:NSStrikethroughStyleAttributeName]) { + mQtFont->setStrikeOut([number intValue] != NSUnderlineStyleNone); + } + } + + if (mPriv) + mPriv->updateSampleFont(*mQtFont); +} + +- (BOOL)windowShouldClose:(id)window +{ + Q_UNUSED(window); + if (mPanelHackedWithButtons) { + [self onCancelClicked]; + } else { + [self finishOffWithCode:NSCancelButton]; + } + return true; +} + +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize +{ + if (mFontPanel == window) { + proposedFrameSize = [static_cast<id <NSWindowDelegate> >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize]; + } else { + /* + Ugly hack: NSFontPanel rearranges the layout of its main + component in windowWillResize:toSize:. So we temporarily + restore the stolen content view to its rightful owner, + call windowWillResize:toSize:, and steal the content view + again. + */ + [mStolenContentView removeFromSuperview]; + [mFontPanel setContentView:mStolenContentView]; + NSSize extraSize = [self dialogExtraSize]; + proposedFrameSize.width -= extraSize.width; + proposedFrameSize.height -= extraSize.height; + proposedFrameSize = [static_cast<id <NSWindowDelegate> >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize]; + NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize }; + [mFontPanel setFrame:frameRect display:NO]; + [mFontPanel setContentView:0]; + [[window contentView] addSubview:mStolenContentView]; + proposedFrameSize.width += extraSize.width; + proposedFrameSize.height += extraSize.height; + } + if (mPanelHackedWithButtons) { + NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize }; + NSRect contentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[window styleMask]]; + [self relayoutToContentSize:contentRect.size]; + } + return proposedFrameSize; +} + +- (void)relayout +{ + [self relayoutToContentSize:[[mStolenContentView superview] frame].size]; +} + +- (void)relayoutToContentSize:(NSSize)frameSize +{ + Q_ASSERT(mPanelHackedWithButtons); + + [mOkButton sizeToFit]; + NSSize okSizeHint = [mOkButton frame].size; + + [mCancelButton sizeToFit]; + NSSize cancelSizeHint = [mCancelButton frame].size; + + const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth, + qMax(okSizeHint.width, cancelSizeHint.width)), + CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5)); + const CGFloat ButtonHeight = qMax(ButtonMinHeight, + qMax(okSizeHint.height, cancelSizeHint.height)); + + const CGFloat X = DialogSideMargin; + const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin; + + NSRect okRect = { { frameSize.width - ButtonSideMargin - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mOkButton setFrame:okRect]; + [mOkButton setNeedsDisplay:YES]; + + NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mCancelButton setFrame:cancelRect]; + [mCancelButton setNeedsDisplay:YES]; + + NSRect stolenCVRect = { { X, Y }, + { frameSize.width - X - X, frameSize.height - Y - DialogTopMargin } }; + [mStolenContentView setFrame:stolenCVRect]; + [mStolenContentView setNeedsDisplay:YES]; + + [[mStolenContentView superview] setNeedsDisplay:YES]; +} + +- (void)onOkClicked +{ + Q_ASSERT(mPanelHackedWithButtons); + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + [self setQtFont:qfontForCocoaFont([fontManager convertFont:[fontManager selectedFont]], + *mQtFont)]; + [self finishOffWithCode:NSOKButton]; +} + +- (void)onCancelClicked +{ + Q_ASSERT(mPanelHackedWithButtons); + [self finishOffWithCode:NSCancelButton]; +} + +- (NSFontPanel *)fontPanel +{ + return mFontPanel; +} + +- (NSWindow *)actualPanel +{ + return [mStolenContentView window]; +} + +- (NSSize)dialogExtraSize +{ + // this must be recomputed each time, because sometimes the + // NSFontPanel has the NSDocModalWindowMask flag set, and sometimes + // not -- which affects the frame rect vs. content rect measurements + + // take the different frame rectangles into account for dialogExtra{Width,Height} + NSRect someRect = { { 0.0, 0.0 }, { 100000.0, 100000.0 } }; + NSRect sharedFontPanelContentRect = [mFontPanel contentRectForFrameRect:someRect]; + NSRect ourPanelContentRect = [NSWindow contentRectForFrameRect:someRect styleMask:StyleMask]; + + NSSize result = { mDialogExtraWidth, mDialogExtraHeight }; + result.width -= ourPanelContentRect.size.width - sharedFontPanelContentRect.size.width; + result.height -= ourPanelContentRect.size.height - sharedFontPanelContentRect.size.height; + return result; +} + +- (void)setQtFont:(const QFont &)newFont +{ + delete mQtFont; + mQtFont = new QFont(newFont); +} + +- (QFont)qtFont +{ + return *mQtFont; +} + +- (void)finishOffWithCode:(NSInteger)code +{ +#ifdef QT_MAC_USE_COCOA + QFontDialog *q = mPriv->fontDialog(); + if (QWidget *parent = q->parentWidget()) { + if (parent->isWindow()) { + [qt_mac_window_for(parent) removeChildWindow:[mStolenContentView window]]; + } + } +#endif + + if(code == NSOKButton) + mPriv->sampleEdit->setFont([self qtFont]); + + if (mAppModal) { + mReturnCode = code; + [NSApp stopModalWithCode:code]; + } else { + if (code == NSOKButton) + mPriv->fontDialog()->accept(); + else + mPriv->fontDialog()->reject(); + } +} + +- (void)cleanUpAfterMyself +{ + if (mPanelHackedWithButtons) { + NSView *ourContentView = [mFontPanel contentView]; + + // return stolen stuff to its rightful owner + [mStolenContentView removeFromSuperview]; + [mFontPanel setContentView:mStolenContentView]; + + [mOkButton release]; + [mCancelButton release]; + [ourContentView release]; + } + [mFontPanel setDelegate:nil]; + [[NSFontManager sharedFontManager] setDelegate:nil]; +#ifdef QT_MAC_USE_COCOA + [[NSFontManager sharedFontManager] setTarget:nil]; +#endif +} +@end + +QT_BEGIN_NAMESPACE + +void QFontDialogPrivate::closeCocoaFontPanel() +{ + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate); + NSWindow *ourPanel = [theDelegate actualPanel]; + [ourPanel close]; + if ([theDelegate isAppModal]) + [ourPanel release]; + [theDelegate cleanUpAfterMyself]; + [theDelegate release]; + this->delegate = 0; + sharedFontPanelAvailable = true; +} + +void QFontDialogPrivate::setFont(void *delegate, const QFont &font) +{ + QMacCocoaAutoReleasePool pool; + QFontEngine *fe = font.d->engineForScript(QUnicodeTables::Common); + NSFontManager *mgr = [NSFontManager sharedFontManager]; + const NSFont *nsFont = 0; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (qstrcmp(fe->name(), "CoreText") == 0) { + nsFont = reinterpret_cast<const NSFont *>(static_cast<QCoreTextFontEngineMulti *>(fe)->ctfont); + } else +#endif + { + int weight = 5; + NSFontTraitMask mask = 0; + if (font.style() == QFont::StyleItalic) { + mask |= NSItalicFontMask; + } + if (font.weight() == QFont::Bold) { + weight = 9; + mask |= NSBoldFontMask; + } + + NSFontManager *mgr = [NSFontManager sharedFontManager]; + QFontInfo fontInfo(font); + nsFont = [mgr fontWithFamily:qt_mac_QStringToNSString(fontInfo.family()) + traits:mask + weight:weight + size:fontInfo.pointSize()]; + } + + [mgr setSelectedFont:const_cast<NSFont *>(nsFont) isMultiple:NO]; + [static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate) setQtFont:font]; +} + +void QFontDialogPrivate::createNSFontPanelDelegate() +{ + if (delegate) + return; + + sharedFontPanelAvailable = false; + QMacCocoaAutoReleasePool pool; + bool sharedFontPanelExisted = [NSFontPanel sharedFontPanelExists]; + NSFontPanel *sharedFontPanel = [NSFontPanel sharedFontPanel]; + [sharedFontPanel setHidesOnDeactivate:false]; + + // hack to ensure that QCocoaApplication's validModesForFontPanel: + // implementation is honored + if (!sharedFontPanelExisted) { + [sharedFontPanel makeKeyAndOrderFront:sharedFontPanel]; + [sharedFontPanel close]; + } + + NSPanel *ourPanel = 0; + NSView *stolenContentView = 0; + NSButton *okButton = 0; + NSButton *cancelButton = 0; + + CGFloat dialogExtraWidth = 0.0; + CGFloat dialogExtraHeight = 0.0; + + // compute dialogExtra{Width,Height} + dialogExtraWidth = 2.0 * DialogSideMargin; + dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight + ButtonBottomMargin; + + // compute initial contents rectangle + NSRect contentRect = [sharedFontPanel contentRectForFrameRect:[sharedFontPanel frame]]; + contentRect.size.width += dialogExtraWidth; + contentRect.size.height += dialogExtraHeight; + + // create the new panel + ourPanel = [[NSPanel alloc] initWithContentRect:contentRect + styleMask:StyleMask + backing:NSBackingStoreBuffered + defer:YES]; + [ourPanel setReleasedWhenClosed:YES]; + stolenContentView = [sharedFontPanel contentView]; + + // steal the font panel's contents view + [stolenContentView retain]; + [sharedFontPanel setContentView:0]; + + { + // create a new content view and add the stolen one as a subview + NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect]; + [ourContentView addSubview:stolenContentView]; + + // create OK and Cancel buttons and add these as subviews + okButton = macCreateButton("&OK", ourContentView); + cancelButton = macCreateButton("Cancel", ourContentView); + + [ourPanel setContentView:ourContentView]; + [ourPanel setDefaultButtonCell:[okButton cell]]; + } + + // create the delegate and set it + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = [[QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) alloc] initWithFontPanel:sharedFontPanel + stolenContentView:stolenContentView + okButton:okButton + cancelButton:cancelButton + priv:this + extraWidth:dialogExtraWidth + extraHeight:dialogExtraHeight]; + delegate = del; + [ourPanel setDelegate:del]; + + [[NSFontManager sharedFontManager] setDelegate:del]; +#ifdef QT_MAC_USE_COCOA + [[NSFontManager sharedFontManager] setTarget:del]; +#endif + setFont(del, q_func()->currentFont()); + + { + // hack to get correct initial layout + NSRect frameRect = [ourPanel frame]; + frameRect.size.width += 1.0; + [ourPanel setFrame:frameRect display:NO]; + frameRect.size.width -= 1.0; + frameRect.size = [del windowWillResize:ourPanel toSize:frameRect.size]; + [ourPanel setFrame:frameRect display:NO]; + [ourPanel center]; + } + [del setSubwindowStacking]; + NSString *title = @"Select font"; + [ourPanel setTitle:title]; +} + +void QFontDialogPrivate::mac_nativeDialogModalHelp() +{ + // Copied from QFileDialogPrivate + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most QEventLoop has been + // interrupted, and the second-most event loop has not yet been reactivated (regardless + // if [NSApp run] is still on the stack)), showing a native modal dialog will fail. + if (nativeDialogInUse) { + Q_Q(QFontDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +// The problem with the native font dialog is that OS X does not +// offer a proper dialog, but a panel (i.e. without Ok and Cancel buttons). +// This means we need to "construct" a native dialog by taking the panel +// and "adding" the buttons. +void QFontDialogPrivate::_q_macRunNativeAppModalPanel() +{ + createNSFontPanelDelegate(); + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate); + [del runApplicationModalPanel]; +} + +bool QFontDialogPrivate::showCocoaFontPanel() +{ + if (!sharedFontPanelAvailable) + return false; + + Q_Q(QFontDialog); + QMacCocoaAutoReleasePool pool; + createNSFontPanelDelegate(); + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate); + if (qt_mac_is_macsheet(q)) + [del showWindowModalSheet:q->parentWidget()]; + else + [del showModelessPanel]; + return true; +} + +bool QFontDialogPrivate::hideCocoaFontPanel() +{ + if (!delegate){ + // Nothing to do. We return false to leave the question + // open regarding whether or not to go native: + return false; + } else { + closeCocoaFontPanel(); + // Even when we hide it, we are still using a + // native dialog, so return true: + return true; + } +} +bool QFontDialogPrivate::setVisible_sys(bool visible) +{ + Q_Q(QFontDialog); + if (!visible == q->isHidden()) + return false; + + return visible ? showCocoaFontPanel() : hideCocoaFontPanel(); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qfontdialog_p.h b/src/widgets/dialogs/qfontdialog_p.h new file mode 100644 index 0000000000..349e2d3fcb --- /dev/null +++ b/src/widgets/dialogs/qfontdialog_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDIALOG_P_H +#define QFONTDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qdialog_p.h" +#include "qfontdatabase.h" +#include "qfontdialog.h" + +#ifndef QT_NO_FONTDIALOG + +QT_BEGIN_NAMESPACE + +class QBoxLayout; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QFontListView; +class QGroupBox; +class QLabel; +class QLineEdit; + +class QFontDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QFontDialog) + +public: + inline QFontDialogPrivate() + : writingSystem(QFontDatabase::Any) + { } + + void updateFamilies(); + void updateStyles(); + void updateSizes(); + + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, + const QString &title, QFontDialog::FontDialogOptions options); + + void init(); + void _q_sizeChanged(const QString &); + void _q_familyHighlighted(int); + void _q_writingSystemHighlighted(int); + void _q_styleHighlighted(int); + void _q_sizeHighlighted(int); + void _q_updateSample(); + void updateSampleFont(const QFont &newFont); + void retranslateStrings(); + + QLabel *familyAccel; + QLineEdit *familyEdit; + QFontListView *familyList; + + QLabel *styleAccel; + QLineEdit *styleEdit; + QFontListView *styleList; + + QLabel *sizeAccel; + QLineEdit *sizeEdit; + QFontListView *sizeList; + + QGroupBox *effects; + QCheckBox *strikeout; + QCheckBox *underline; + QComboBox *color; + + QGroupBox *sample; + QLineEdit *sampleEdit; + + QLabel *writingSystemAccel; + QComboBox *writingSystemCombo; + + QBoxLayout *buttonLayout; + QBoxLayout *effectsLayout; + QBoxLayout *sampleLayout; + QBoxLayout *sampleEditLayout; + + QDialogButtonBox *buttonBox; + + QFontDatabase fdb; + QString family; + QFontDatabase::WritingSystem writingSystem; + QString style; + int size; + bool smoothScalable; + QFont selectedFont; + QFontDialog::FontDialogOptions opts; + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + +#ifdef Q_WS_MAC + static void setFont(void *delegate, const QFont &font); + + inline void done(int result) { q_func()->done(result); } + inline QFontDialog *fontDialog() { return q_func(); } + + void *delegate; + void closeCocoaFontPanel(); + bool nativeDialogInUse; + bool canBeNativeDialog(); + bool setVisible_sys(bool visible); + void createNSFontPanelDelegate(); + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); + bool showCocoaFontPanel(); + bool hideCocoaFontPanel(); + + static bool sharedFontPanelAvailable; +#endif +}; + +#endif // QT_NO_FONTDIALOG + +QT_END_NAMESPACE + +#endif // QFONTDIALOG_P_H diff --git a/src/widgets/dialogs/qfscompleter_p.h b/src/widgets/dialogs/qfscompleter_p.h new file mode 100644 index 0000000000..e078542cdc --- /dev/null +++ b/src/widgets/dialogs/qfscompleter_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSCOMPLETOR_P_H +#define QFSCOMPLETOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qcompleter.h" +#include <QtWidgets/qfilesystemmodel.h> +QT_BEGIN_NAMESPACE +#ifndef QT_NO_FSCOMPLETER + +/*! + QCompleter that can deal with QFileSystemModel + */ +class Q_WIDGETS_EXPORT QFSCompleter : public QCompleter { +public: + QFSCompleter(QFileSystemModel *model, QObject *parent = 0) + : QCompleter(model, parent), proxyModel(0), sourceModel(model) + { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + setCaseSensitivity(Qt::CaseInsensitive); +#endif + } + QString pathFromIndex(const QModelIndex &index) const; + QStringList splitPath(const QString& path) const; + + QAbstractProxyModel *proxyModel; + QFileSystemModel *sourceModel; +}; +#endif // QT_NO_FSCOMPLETER +QT_END_NAMESPACE +#endif // QFSCOMPLETOR_P_H + diff --git a/src/widgets/dialogs/qinputdialog.cpp b/src/widgets/dialogs/qinputdialog.cpp new file mode 100644 index 0000000000..5ca947ccbd --- /dev/null +++ b/src/widgets/dialogs/qinputdialog.cpp @@ -0,0 +1,1489 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qinputdialog.h" + +#ifndef QT_NO_INPUTDIALOG + +#include "qapplication.h" +#include "qcombobox.h" +#include "qdialogbuttonbox.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qlineedit.h" +#include "qlistwidget.h" +#include "qpushbutton.h" +#include "qspinbox.h" +#include "qstackedlayout.h" +#include "qvalidator.h" +#include "qevent.h" +#include "qdialog_p.h" + +QT_USE_NAMESPACE + +static const char *signalForMember(const char *member) +{ + static const int NumCandidates = 4; + static const char * const candidateSignals[NumCandidates] = { + SIGNAL(textValueSelected(QString)), + SIGNAL(intValueSelected(int)), + SIGNAL(doubleValueSelected(double)), + SIGNAL(accepted()) + }; + + QByteArray normalizedMember(QMetaObject::normalizedSignature(member)); + + int i = 0; + while (i < NumCandidates - 1) { // sic + if (QMetaObject::checkConnectArgs(candidateSignals[i], normalizedMember)) + break; + ++i; + } + return candidateSignals[i]; +} + +QT_BEGIN_NAMESPACE + +/* + These internal classes add extra validation to QSpinBox and QDoubleSpinBox by emitting + textChanged(bool) after events that may potentially change the visible text. Return or + Enter key presses are not propagated if the visible text is invalid. Instead, the visible + text is modified to the last valid value. +*/ +class QInputDialogSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + QInputDialogSpinBox(QWidget *parent) + : QSpinBox(parent) { + connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(notifyTextChanged())); + connect(this, SIGNAL(editingFinished()), this, SLOT(notifyTextChanged())); + } + +signals: + void textChanged(bool); + +private slots: + void notifyTextChanged() { emit textChanged(hasAcceptableInput()); } + +private: + void keyPressEvent(QKeyEvent *event) { + if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && !hasAcceptableInput()) { +#ifndef QT_NO_PROPERTIES + setProperty("value", property("value")); +#endif + } else { + QSpinBox::keyPressEvent(event); + } + notifyTextChanged(); + } + + void mousePressEvent(QMouseEvent *event) { + QSpinBox::mousePressEvent(event); + notifyTextChanged(); + } +}; + +class QInputDialogDoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + QInputDialogDoubleSpinBox(QWidget *parent = 0) + : QDoubleSpinBox(parent) { + connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(notifyTextChanged())); + connect(this, SIGNAL(editingFinished()), this, SLOT(notifyTextChanged())); + } + +signals: + void textChanged(bool); + +private slots: + void notifyTextChanged() { emit textChanged(hasAcceptableInput()); } + +private: + void keyPressEvent(QKeyEvent *event) { + if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && !hasAcceptableInput()) { +#ifndef QT_NO_PROPERTIES + setProperty("value", property("value")); +#endif + } else { + QDoubleSpinBox::keyPressEvent(event); + } + notifyTextChanged(); + } + + void mousePressEvent(QMouseEvent *event) { + QDoubleSpinBox::mousePressEvent(event); + notifyTextChanged(); + } +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qinputdialog.moc" +QT_END_INCLUDE_NAMESPACE + +class QInputDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QInputDialog) + +public: + QInputDialogPrivate(); + + void ensureLayout(); + void ensureLineEdit(); + void ensureComboBox(); + void ensureListView(); + void ensureIntSpinBox(); + void ensureDoubleSpinBox(); + void ensureEnabledConnection(QAbstractSpinBox *spinBox); + void setInputWidget(QWidget *widget); + void chooseRightTextInputWidget(); + void setComboBoxText(const QString &text); + void setListViewText(const QString &text); + QString listViewText() const; + void ensureLayout() const { const_cast<QInputDialogPrivate *>(this)->ensureLayout(); } + bool useComboBoxOrListView() const { return comboBox && comboBox->count() > 0; } + void _q_textChanged(const QString &text); + void _q_currentRowChanged(const QModelIndex &newIndex, const QModelIndex &oldIndex); + + mutable QLabel *label; + mutable QDialogButtonBox *buttonBox; + mutable QLineEdit *lineEdit; + mutable QSpinBox *intSpinBox; + mutable QDoubleSpinBox *doubleSpinBox; + mutable QComboBox *comboBox; + mutable QListView *listView; + mutable QWidget *inputWidget; + mutable QVBoxLayout *mainLayout; + QInputDialog::InputDialogOptions opts; + QString textValue; + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +QInputDialogPrivate::QInputDialogPrivate() + : label(0), buttonBox(0), lineEdit(0), intSpinBox(0), doubleSpinBox(0), comboBox(0), listView(0), + inputWidget(0), mainLayout(0) +{ +} + +void QInputDialogPrivate::ensureLayout() +{ + Q_Q(QInputDialog); + + if (mainLayout) + return; + + if (!inputWidget) { + ensureLineEdit(); + inputWidget = lineEdit; + } + + if (!label) + label = new QLabel(QInputDialog::tr("Enter a value:"), q); +#ifndef QT_NO_SHORTCUT + label->setBuddy(inputWidget); +#endif + label->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q); + QObject::connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + QObject::connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + + mainLayout = new QVBoxLayout(q); + //we want to let the input dialog grow to available size on Symbian. +#ifndef Q_OS_SYMBIAN + mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); +#else + label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +#endif + mainLayout->addWidget(label); + mainLayout->addWidget(inputWidget); + mainLayout->addWidget(buttonBox); + ensureEnabledConnection(qobject_cast<QAbstractSpinBox *>(inputWidget)); + inputWidget->show(); +} + +void QInputDialogPrivate::ensureLineEdit() +{ + Q_Q(QInputDialog); + if (!lineEdit) { + lineEdit = new QLineEdit(q); +#ifndef QT_NO_IM + qt_widget_private(lineEdit)->inheritsInputMethodHints = 1; +#endif + lineEdit->hide(); + QObject::connect(lineEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_textChanged(QString))); + } +} + +void QInputDialogPrivate::ensureComboBox() +{ + Q_Q(QInputDialog); + if (!comboBox) { + comboBox = new QComboBox(q); +#ifndef QT_NO_IM + qt_widget_private(comboBox)->inheritsInputMethodHints = 1; +#endif + comboBox->hide(); + QObject::connect(comboBox, SIGNAL(editTextChanged(QString)), + q, SLOT(_q_textChanged(QString))); + QObject::connect(comboBox, SIGNAL(currentIndexChanged(QString)), + q, SLOT(_q_textChanged(QString))); + } +} + +void QInputDialogPrivate::ensureListView() +{ + Q_Q(QInputDialog); + if (!listView) { + ensureComboBox(); + + listView = new QListView(q); + listView->hide(); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + listView->setModel(comboBox->model()); + listView->setCurrentIndex(QModelIndex()); // ### + QObject::connect(listView->selectionModel(), + SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_currentRowChanged(QModelIndex,QModelIndex))); + } +} + +void QInputDialogPrivate::ensureIntSpinBox() +{ + Q_Q(QInputDialog); + if (!intSpinBox) { + intSpinBox = new QInputDialogSpinBox(q); + intSpinBox->hide(); + QObject::connect(intSpinBox, SIGNAL(valueChanged(int)), + q, SIGNAL(intValueChanged(int))); + } +} + +void QInputDialogPrivate::ensureDoubleSpinBox() +{ + Q_Q(QInputDialog); + if (!doubleSpinBox) { + doubleSpinBox = new QInputDialogDoubleSpinBox(q); + doubleSpinBox->hide(); + QObject::connect(doubleSpinBox, SIGNAL(valueChanged(double)), + q, SIGNAL(doubleValueChanged(double))); + } +} + +void QInputDialogPrivate::ensureEnabledConnection(QAbstractSpinBox *spinBox) +{ + if (spinBox) { + QAbstractButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + QObject::connect(spinBox, SIGNAL(textChanged(bool)), okButton, SLOT(setEnabled(bool)), Qt::UniqueConnection); + } +} + +void QInputDialogPrivate::setInputWidget(QWidget *widget) +{ + Q_ASSERT(widget); + if (inputWidget == widget) + return; + + if (mainLayout) { + Q_ASSERT(inputWidget); + mainLayout->removeWidget(inputWidget); + inputWidget->hide(); + mainLayout->insertWidget(1, widget); + widget->show(); + + // disconnect old input widget + QAbstractButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + if (QAbstractSpinBox *spinBox = qobject_cast<QAbstractSpinBox *>(inputWidget)) + QObject::disconnect(spinBox, SIGNAL(textChanged(bool)), okButton, SLOT(setEnabled(bool))); + + // connect new input widget and update enabled state of OK button + QAbstractSpinBox *spinBox = qobject_cast<QAbstractSpinBox *>(widget); + ensureEnabledConnection(spinBox); + okButton->setEnabled(!spinBox || spinBox->hasAcceptableInput()); + } + + inputWidget = widget; + + // synchronize the text shown in the new text editor with the current + // textValue + if (widget == lineEdit) { + lineEdit->setText(textValue); + } else if (widget == comboBox) { + setComboBoxText(textValue); + } else if (widget == listView) { + setListViewText(textValue); + ensureLayout(); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(listView->selectionModel()->hasSelection()); + } +} + +void QInputDialogPrivate::chooseRightTextInputWidget() +{ + QWidget *widget; + + if (useComboBoxOrListView()) { + if ((opts & QInputDialog::UseListViewForComboBoxItems) && !comboBox->isEditable()) { + ensureListView(); + widget = listView; + } else { + widget = comboBox; + } + } else { + ensureLineEdit(); + widget = lineEdit; + } + + setInputWidget(widget); + + if (inputWidget == comboBox) { + _q_textChanged(comboBox->currentText()); + } else if (inputWidget == listView) { + _q_textChanged(listViewText()); + } +} + +void QInputDialogPrivate::setComboBoxText(const QString &text) +{ + int index = comboBox->findText(text); + if (index != -1) { + comboBox->setCurrentIndex(index); + } else if (comboBox->isEditable()) { + comboBox->setEditText(text); + } +} + +void QInputDialogPrivate::setListViewText(const QString &text) +{ + int row = comboBox->findText(text); + if (row != -1) { + QModelIndex index(comboBox->model()->index(row, 0)); + listView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear + | QItemSelectionModel::SelectCurrent); + } +} + +QString QInputDialogPrivate::listViewText() const +{ + if (listView->selectionModel()->hasSelection()) { + int row = listView->selectionModel()->selectedRows().value(0).row(); + return comboBox->itemText(row); + } else { + return QString(); + } +} + +void QInputDialogPrivate::_q_textChanged(const QString &text) +{ + Q_Q(QInputDialog); + if (textValue != text) { + textValue = text; + emit q->textValueChanged(text); + } +} + +void QInputDialogPrivate::_q_currentRowChanged(const QModelIndex &newIndex, + const QModelIndex & /* oldIndex */) +{ + _q_textChanged(comboBox->model()->data(newIndex).toString()); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} + +/*! + \class QInputDialog + \brief The QInputDialog class provides a simple convenience dialog to get a + single value from the user. + \ingroup standard-dialogs + + + The input value can be a string, a number or an item from a list. A label + must be set to tell the user what they should enter. + + Four static convenience functions are provided: getText(), getInt(), + getDouble(), and getItem(). All the functions can be used in a similar way, + for example: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 3 + + The \c ok variable is set to true if the user clicks \gui OK; otherwise it + is set to false. + + \img inputdialogs.png Input Dialogs + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows how to use + QInputDialog as well as other built-in Qt dialogs. + + \sa QMessageBox, {Standard Dialogs Example} +*/ + +/*! + \enum QInputDialog::InputMode + \since 4.5 + + This enum describes the different modes of input that can be selected for + the dialog. + + \value TextInput Used to input text strings. + \value IntInput Used to input integers. + \value DoubleInput Used to input floating point numbers with double + precision accuracy. + + \sa inputMode +*/ + +/*! + \since 4.5 + + Constructs a new input dialog with the given \a parent and window \a flags. +*/ +QInputDialog::QInputDialog(QWidget *parent, Qt::WindowFlags flags) + : QDialog(*new QInputDialogPrivate, parent, flags) +{ +} + +/*! + \since 4.5 + + Destroys the input dialog. +*/ +QInputDialog::~QInputDialog() +{ +} + +/*! + \since 4.5 + + \property QInputDialog::inputMode + + \brief the mode used for input + + This property help determines which widget is used for entering input into + the dialog. +*/ +void QInputDialog::setInputMode(InputMode mode) +{ + Q_D(QInputDialog); + + QWidget *widget; + + /* + Warning: Some functions in QInputDialog rely on implementation details + of the code below. Look for the comments that accompany the calls to + setInputMode() throughout this file before you change the code below. + */ + + switch (mode) { + case IntInput: + d->ensureIntSpinBox(); + widget = d->intSpinBox; + break; + case DoubleInput: + d->ensureDoubleSpinBox(); + widget = d->doubleSpinBox; + break; + default: + Q_ASSERT(mode == TextInput); + d->chooseRightTextInputWidget(); + return; + } + + d->setInputWidget(widget); +} + +QInputDialog::InputMode QInputDialog::inputMode() const +{ + Q_D(const QInputDialog); + + if (d->inputWidget) { + if (d->inputWidget == d->intSpinBox) { + return IntInput; + } else if (d->inputWidget == d->doubleSpinBox) { + return DoubleInput; + } + } + + return TextInput; +} + +/*! + \since 4.5 + + \property QInputDialog::labelText + + \brief the text to for the label to describe what needs to be input +*/ +void QInputDialog::setLabelText(const QString &text) +{ + Q_D(QInputDialog); + if (!d->label) { + d->label = new QLabel(text, this); + } else { + d->label->setText(text); + } +#ifdef Q_OS_SYMBIAN + d->label->setWordWrap(true); +#endif +} + +QString QInputDialog::labelText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->label->text(); +} + +/*! + \enum QInputDialog::InputDialogOption + + \since 4.5 + + This enum specifies various options that affect the look and feel + of an input dialog. + + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value UseListViewForComboBoxItems Use a QListView rather than a non-editable QComboBox for + displaying the items set with setComboBoxItems(). + + \sa options, setOption(), testOption() +*/ + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QInputDialog::setOption(InputDialogOption option, bool on) +{ + Q_D(QInputDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QInputDialog::testOption(InputDialogOption option) const +{ + Q_D(const QInputDialog); + return (d->opts & option) != 0; +} + +/*! + \property QInputDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + \sa setOption(), testOption() +*/ +void QInputDialog::setOptions(InputDialogOptions options) +{ + Q_D(QInputDialog); + + InputDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->ensureLayout(); + + if (changed & NoButtons) + d->buttonBox->setVisible(!(options & NoButtons)); + if ((changed & UseListViewForComboBoxItems) && inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +QInputDialog::InputDialogOptions QInputDialog::options() const +{ + Q_D(const QInputDialog); + return d->opts; +} + +/*! + \since 4.5 + + \property QInputDialog::textValue + + \brief the text value for the input dialog + + This property is only relevant when the input dialog is used in + TextInput mode. +*/ +void QInputDialog::setTextValue(const QString &text) +{ + Q_D(QInputDialog); + + setInputMode(TextInput); + if (d->inputWidget == d->lineEdit) { + d->lineEdit->setText(text); + } else if (d->inputWidget == d->comboBox) { + d->setComboBoxText(text); + } else { + d->setListViewText(text); + } +} + +QString QInputDialog::textValue() const +{ + Q_D(const QInputDialog); + return d->textValue; +} + +/*! + \since 4.5 + + \property QInputDialog::textEchoMode + + \brief the echo mode for the text value + + This property is only relevant when the input dialog is used in + TextInput mode. +*/ +void QInputDialog::setTextEchoMode(QLineEdit::EchoMode mode) +{ + Q_D(QInputDialog); + d->ensureLineEdit(); + d->lineEdit->setEchoMode(mode); +} + +QLineEdit::EchoMode QInputDialog::textEchoMode() const +{ + Q_D(const QInputDialog); + if (d->lineEdit) { + return d->lineEdit->echoMode(); + } else { + return QLineEdit::Normal; + } +} + +/*! + \since 4.5 + + \property QInputDialog::comboBoxEditable + + \brief whether or not the combo box is used in the input dialog is editable +*/ +void QInputDialog::setComboBoxEditable(bool editable) +{ + Q_D(QInputDialog); + d->ensureComboBox(); + d->comboBox->setEditable(editable); + if (inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +bool QInputDialog::isComboBoxEditable() const +{ + Q_D(const QInputDialog); + if (d->comboBox) { + return d->comboBox->isEditable(); + } else { + return false; + } +} + +/*! + \since 4.5 + + \property QInputDialog::comboBoxItems + + \brief the items used in the combobox for the input dialog +*/ +void QInputDialog::setComboBoxItems(const QStringList &items) +{ + Q_D(QInputDialog); + + d->ensureComboBox(); + d->comboBox->blockSignals(true); + d->comboBox->clear(); + d->comboBox->addItems(items); + d->comboBox->blockSignals(false); + + if (inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +QStringList QInputDialog::comboBoxItems() const +{ + Q_D(const QInputDialog); + QStringList result; + if (d->comboBox) { + const int count = d->comboBox->count(); + for (int i = 0; i < count; ++i) + result.append(d->comboBox->itemText(i)); + } + return result; +} + +/*! + \property QInputDialog::intValue + \since 4.5 + \brief the current integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntValue(int value) +{ + Q_D(QInputDialog); + setInputMode(IntInput); + d->intSpinBox->setValue(value); +} + +int QInputDialog::intValue() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->value(); + } else { + return 0; + } +} + +/*! + \property QInputDialog::intMinimum + \since 4.5 + \brief the minimum integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntMinimum(int min) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setMinimum(min); +} + +int QInputDialog::intMinimum() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->minimum(); + } else { + return 0; + } +} + +/*! + \property QInputDialog::intMaximum + \since 4.5 + \brief the maximum integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntMaximum(int max) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setMaximum(max); +} + +int QInputDialog::intMaximum() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->maximum(); + } else { + return 99; + } +} + +/*! + Sets the range of integer values accepted by the dialog when used in + IntInput mode, with minimum and maximum values specified by \a min and + \a max respectively. +*/ +void QInputDialog::setIntRange(int min, int max) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setRange(min, max); +} + +/*! + \property QInputDialog::intStep + \since 4.5 + \brief the step by which the integer value is increased and decreased + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntStep(int step) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setSingleStep(step); +} + +int QInputDialog::intStep() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->singleStep(); + } else { + return 1; + } +} + +/*! + \property QInputDialog::doubleValue + \since 4.5 + \brief the current double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleValue(double value) +{ + Q_D(QInputDialog); + setInputMode(DoubleInput); + d->doubleSpinBox->setValue(value); +} + +double QInputDialog::doubleValue() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->value(); + } else { + return 0.0; + } +} + +/*! + \property QInputDialog::doubleMinimum + \since 4.5 + \brief the minimum double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleMinimum(double min) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setMinimum(min); +} + +double QInputDialog::doubleMinimum() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->minimum(); + } else { + return 0.0; + } +} + +/*! + \property QInputDialog::doubleMaximum + \since 4.5 + \brief the maximum double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleMaximum(double max) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setMaximum(max); +} + +double QInputDialog::doubleMaximum() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->maximum(); + } else { + return 99.99; + } +} + +/*! + Sets the range of double precision floating point values accepted by the + dialog when used in DoubleInput mode, with minimum and maximum values + specified by \a min and \a max respectively. +*/ +void QInputDialog::setDoubleRange(double min, double max) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setRange(min, max); +} + +/*! + \since 4.5 + + \property QInputDialog::doubleDecimals + + \brief sets the percision of the double spinbox in decimals + + \sa QDoubleSpinBox::setDecimals() +*/ +void QInputDialog::setDoubleDecimals(int decimals) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setDecimals(decimals); +} + +int QInputDialog::doubleDecimals() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->decimals(); + } else { + return 2; + } +} + +/*! + \since 4.5 + + \property QInputDialog::okButtonText + + \brief the text for the button used to accept the entry in the dialog +*/ +void QInputDialog::setOkButtonText(const QString &text) +{ + Q_D(const QInputDialog); + d->ensureLayout(); + d->buttonBox->button(QDialogButtonBox::Ok)->setText(text); +} + +QString QInputDialog::okButtonText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->buttonBox->button(QDialogButtonBox::Ok)->text(); +} + +/*! + \since 4.5 + + \property QInputDialog::cancelButtonText + \brief the text for the button used to cancel the dialog +*/ +void QInputDialog::setCancelButtonText(const QString &text) +{ + Q_D(const QInputDialog); + d->ensureLayout(); + d->buttonBox->button(QDialogButtonBox::Cancel)->setText(text); +} + +QString QInputDialog::cancelButtonText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->buttonBox->button(QDialogButtonBox::Cancel)->text(); +} + +/*! + \since 4.5 + \overload + + This function connects one of its signals to the slot specified by \a receiver + and \a member. The specific signal depends on the arguments that are specified + in \a member. These are: + + \list + \o textValueSelected() if \a member has a QString for its first argument. + \o intValueSelected() if \a member has an int for its first argument. + \o doubleValueSelected() if \a member has a double for its first argument. + \o accepted() if \a member has NO arguments. + \endlist + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QInputDialog::open(QObject *receiver, const char *member) +{ + Q_D(QInputDialog); + connect(this, signalForMember(member), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \reimp +*/ +QSize QInputDialog::minimumSizeHint() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return QDialog::minimumSizeHint(); +} + +/*! + \reimp +*/ +QSize QInputDialog::sizeHint() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return QDialog::sizeHint(); +} + +/*! + \reimp +*/ +void QInputDialog::setVisible(bool visible) +{ + Q_D(const QInputDialog); + if (visible) { + d->ensureLayout(); + d->inputWidget->setFocus(); + if (d->inputWidget == d->lineEdit) { + d->lineEdit->selectAll(); + } else if (d->inputWidget == d->intSpinBox) { + d->intSpinBox->selectAll(); + } else if (d->inputWidget == d->doubleSpinBox) { + d->doubleSpinBox->selectAll(); + } + } + QDialog::setVisible(visible); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QInputDialog::done(int result) +{ + Q_D(QInputDialog); + QDialog::done(result); + if (result) { + InputMode mode = inputMode(); + switch (mode) { + case DoubleInput: + emit doubleValueSelected(doubleValue()); + break; + case IntInput: + emit intValueSelected(intValue()); + break; + default: + Q_ASSERT(mode == TextInput); + emit textValueSelected(textValue()); + } + } + if (d->receiverToDisconnectOnClose) { + disconnect(this, signalForMember(d->memberToDisconnectOnClose), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +/*! + Static convenience function to get a string from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a text is the default text which is placed in the line edit. + \a mode is the echo mode the line edit will use. + \a inputMethodHints is the input method hints that will be used in the + edit widget if an input method is active. + + If \a ok is nonnull \e *\a ok will be set to true if the user pressed + \gui OK and to false if the user pressed \gui Cancel. The dialog's parent + is \a parent. The dialog will be modal and uses the specified widget + \a flags. + + If the dialog is accepted, this function returns the text in the dialog's + line edit. If the dialog is rejected, a null QString is returned. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 3 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getInt(), getDouble(), getItem() +*/ + +QString QInputDialog::getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode mode, const QString &text, bool *ok, + Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setTextValue(text); + dialog.setTextEchoMode(mode); + dialog.setInputMethodHints(inputMethodHints); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.textValue(); + } else { + return QString(); + } +} + +/*! + \internal +*/ +// ### Qt 5: Use only the version above. +QString QInputDialog::getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode mode, const QString &text, bool *ok, + Qt::WindowFlags flags) +{ + return getText(parent, title, label, mode, text, ok, flags, Qt::ImhNone); +} + +/*! + \since 4.5 + + Static convenience function to get an integer input from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a value is the default integer which the spinbox will be set to. + \a min and \a max are the minimum and maximum values the user may choose. + \a step is the amount by which the values change as the user presses the + arrow buttons to increment or decrement the value. + + If \a ok is nonnull *\a ok will be set to true if the user pressed \gui OK + and to false if the user pressed \gui Cancel. The dialog's parent is + \a parent. The dialog will be modal and uses the widget \a flags. + + On success, this function returns the integer which has been entered by the + user; on failure, it returns the initial \a value. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 0 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getDouble(), getItem() +*/ + +int QInputDialog::getInt(QWidget *parent, const QString &title, const QString &label, int value, + int min, int max, int step, bool *ok, Qt::WindowFlags flags) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setIntRange(min, max); + dialog.setIntValue(value); + dialog.setIntStep(step); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.intValue(); + } else { + return value; + } +} + +/*! + Static convenience function to get a floating point number from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a value is the default floating point number that the line edit will be + set to. + \a min and \a max are the minimum and maximum values the user may choose. + \a decimals is the maximum number of decimal places the number may have. + + If \a ok is nonnull, *\a ok will be set to true if the user pressed \gui OK + and to false if the user pressed \gui Cancel. The dialog's parent is + \a parent. The dialog will be modal and uses the widget \a flags. + + This function returns the floating point number which has been entered by + the user. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 1 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getInt(), getItem() +*/ + +double QInputDialog::getDouble(QWidget *parent, const QString &title, const QString &label, + double value, double min, double max, int decimals, bool *ok, + Qt::WindowFlags flags) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setDoubleDecimals(decimals); + dialog.setDoubleRange(min, max); + dialog.setDoubleValue(value); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.doubleValue(); + } else { + return value; + } +} + +/*! + Static convenience function to let the user select an item from a string + list. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a items is the string list which is inserted into the combobox. + \a current is the number of the item which should be the current item. + \a inputMethodHints is the input method hints that will be used if the + combobox is editable and an input method is active. + + If \a editable is true the user can enter their own text; otherwise the + user may only select one of the existing items. + + If \a ok is nonnull \e *\a ok will be set to true if the user pressed + \gui OK and to false if the user pressed \gui Cancel. The dialog's parent + is \a parent. The dialog will be modal and uses the widget \a flags. + + This function returns the text of the current item, or if \a editable is + true, the current text of the combobox. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 2 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getInt(), getDouble() +*/ + +QString QInputDialog::getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, bool *ok, + Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints) +{ + QString text(items.value(current)); + + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setComboBoxItems(items); + dialog.setTextValue(text); + dialog.setComboBoxEditable(editable); + dialog.setInputMethodHints(inputMethodHints); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.textValue(); + } else { + return text; + } +} + +/*! + \internal +*/ +// ### Qt 5: Use only the version above. +QString QInputDialog::getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, bool *ok, + Qt::WindowFlags flags) +{ + return getItem(parent, title, label, items, current, editable, ok, flags, Qt::ImhNone); +} + +/*! + \obsolete + + Use getInt() instead. +*/ +int QInputDialog::getInteger(QWidget *parent, const QString &title, const QString &label, + int value, int min, int max, int step, bool *ok, + Qt::WindowFlags flags) +{ + return getInt(parent, title, label, value, min, max, step, ok, flags); +} + +/*! + \fn QString QInputDialog::getText(const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getText(\a parent, \a title, \a label, \a echo, \a text, \a + ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn int QInputDialog::getInteger(const QString &title, const QString &label, int value = 0, + int min = -2147483647, int max = 2147483647, + int step = 1, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + + Call getInteger(\a parent, \a title, \a label, \a value, \a + min, \a max, \a step, \a ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn double QInputDialog::getDouble(const QString &title, const QString &label, double value = 0, + double min = -2147483647, double max = 2147483647, + int decimals = 1, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getDouble(\a parent, \a title, \a label, \a value, \a + min, \a max, \a decimals, \a ok, \a flags). + + The \a name parameter is ignored. +*/ + +/*! + \fn QString QInputDialog::getItem(const QString &title, const QString &label, const QStringList &list, + int current = 0, bool editable = true, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getItem(\a parent, \a title, \a label, \a list, \a current, + \a editable, \a ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn void QInputDialog::doubleValueChanged(double value) + + This signal is emitted whenever the double value changes in the dialog. + The current value is specified by \a value. + + This signal is only relevant when the input dialog is used in + DoubleInput mode. +*/ + +/*! + \fn void QInputDialog::doubleValueSelected(double value) + + This signal is emitted whenever the user selects a double value by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected value is specified by \a value. + + This signal is only relevant when the input dialog is used in + DoubleInput mode. +*/ + +/*! + \fn void QInputDialog::intValueChanged(int value) + + This signal is emitted whenever the integer value changes in the dialog. + The current value is specified by \a value. + + This signal is only relevant when the input dialog is used in + IntInput mode. +*/ + +/*! + \fn void QInputDialog::intValueSelected(int value) + + This signal is emitted whenever the user selects a integer value by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected value is specified by \a value. + + This signal is only relevant when the input dialog is used in + IntInput mode. +*/ + +/*! + \fn void QInputDialog::textValueChanged(const QString &text) + + This signal is emitted whenever the text string changes in the dialog. + The current string is specified by \a text. + + This signal is only relevant when the input dialog is used in + TextInput mode. +*/ + +/*! + \fn void QInputDialog::textValueSelected(const QString &text) + + This signal is emitted whenever the user selects a text string by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected string is specified by \a text. + + This signal is only relevant when the input dialog is used in + TextInput mode. +*/ + +QT_END_NAMESPACE + +#include "moc_qinputdialog.cpp" + +#endif // QT_NO_INPUTDIALOG diff --git a/src/widgets/dialogs/qinputdialog.h b/src/widgets/dialogs/qinputdialog.h new file mode 100644 index 0000000000..51411c7ae1 --- /dev/null +++ b/src/widgets/dialogs/qinputdialog.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QINPUTDIALOG_H +#define QINPUTDIALOG_H + +#include <QtWidgets/qdialog.h> +#include <QtCore/qstring.h> +#include <QtWidgets/qlineedit.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_INPUTDIALOG + +class QInputDialogPrivate; + +class Q_WIDGETS_EXPORT QInputDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QInputDialog) +// Q_ENUMS(InputMode InputDialogOption) + QDOC_PROPERTY(InputMode inputMode READ inputMode WRITE setInputMode) + QDOC_PROPERTY(QString labelText READ labelText WRITE setLabelText) + QDOC_PROPERTY(InputDialogOptions options READ options WRITE setOptions) + QDOC_PROPERTY(QString textValue READ textValue WRITE setTextValue NOTIFY textValueChanged) + QDOC_PROPERTY(int intValue READ intValue WRITE setIntValue NOTIFY intValueChanged) + QDOC_PROPERTY(int doubleValue READ doubleValue WRITE setDoubleValue NOTIFY doubleValueChanged) + QDOC_PROPERTY(QLineEdit::EchoMode textEchoMode READ textEchoMode WRITE setTextEchoMode) + QDOC_PROPERTY(bool comboBoxEditable READ isComboBoxEditable WRITE setComboBoxEditable) + QDOC_PROPERTY(QStringList comboBoxItems READ comboBoxItems WRITE setComboBoxItems) + QDOC_PROPERTY(int intMinimum READ intMinimum WRITE setIntMinimum) + QDOC_PROPERTY(int intMaximum READ intMaximum WRITE setIntMaximum) + QDOC_PROPERTY(int intStep READ intStep WRITE setIntStep) + QDOC_PROPERTY(double doubleMinimum READ doubleMinimum WRITE setDoubleMinimum) + QDOC_PROPERTY(double doubleMaximum READ doubleMaximum WRITE setDoubleMaximum) + QDOC_PROPERTY(int doubleDecimals READ doubleDecimals WRITE setDoubleDecimals) + QDOC_PROPERTY(QString okButtonText READ okButtonText WRITE setOkButtonText) + QDOC_PROPERTY(QString cancelButtonText READ cancelButtonText WRITE setCancelButtonText) + +public: + enum InputDialogOption { + NoButtons = 0x00000001, + UseListViewForComboBoxItems = 0x00000002 + }; + + Q_DECLARE_FLAGS(InputDialogOptions, InputDialogOption) + + enum InputMode { + TextInput, + IntInput, + DoubleInput + }; + + QInputDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QInputDialog(); + + void setInputMode(InputMode mode); + InputMode inputMode() const; + + void setLabelText(const QString &text); + QString labelText() const; + + void setOption(InputDialogOption option, bool on = true); + bool testOption(InputDialogOption option) const; + void setOptions(InputDialogOptions options); + InputDialogOptions options() const; + + void setTextValue(const QString &text); + QString textValue() const; + + void setTextEchoMode(QLineEdit::EchoMode mode); + QLineEdit::EchoMode textEchoMode() const; + + void setComboBoxEditable(bool editable); + bool isComboBoxEditable() const; + + void setComboBoxItems(const QStringList &items); + QStringList comboBoxItems() const; + + void setIntValue(int value); + int intValue() const; + + void setIntMinimum(int min); + int intMinimum() const; + + void setIntMaximum(int max); + int intMaximum() const; + + void setIntRange(int min, int max); + + void setIntStep(int step); + int intStep() const; + + void setDoubleValue(double value); + double doubleValue() const; + + void setDoubleMinimum(double min); + double doubleMinimum() const; + + void setDoubleMaximum(double max); + double doubleMaximum() const; + + void setDoubleRange(double min, double max); + + void setDoubleDecimals(int decimals); + int doubleDecimals() const; + + void setOkButtonText(const QString &text); + QString okButtonText() const; + + void setCancelButtonText(const QString &text); + QString cancelButtonText() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + QSize minimumSizeHint() const; + QSize sizeHint() const; + + void setVisible(bool visible); + +#ifdef Q_QDOC + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, Qt::WindowFlags flags = 0, + Qt::InputMethodHints inputMethodHints = Qt::ImhNone); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current = 0, bool editable = true, + bool *ok = 0, Qt::WindowFlags flags = 0, + Qt::InputMethodHints inputMethodHints = Qt::ImhNone); +#else + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, Qt::WindowFlags flags = 0); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current = 0, bool editable = true, + bool *ok = 0, Qt::WindowFlags flags = 0); + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo, + const QString &text, bool *ok, Qt::WindowFlags flags, + Qt::InputMethodHints inputMethodHints); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, + bool *ok, Qt::WindowFlags flags, + Qt::InputMethodHints inputMethodHints); +#endif + static int getInt(QWidget *parent, const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + static double getDouble(QWidget *parent, const QString &title, const QString &label, double value = 0, + double minValue = -2147483647, double maxValue = 2147483647, + int decimals = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + + // obsolete + static int getInteger(QWidget *parent, const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + +#ifdef QT3_SUPPORT + inline static QT3_SUPPORT QString getText(const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getText(parent, title, label, echo, text, ok, flags); } + inline static QT3_SUPPORT int getInteger(const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getInteger(parent, title, label, value, minValue, maxValue, step, ok, flags); } + inline static QT3_SUPPORT double getDouble(const QString &title, const QString &label, double value = 0, + double minValue = -2147483647, double maxValue = 2147483647, + int decimals = 1, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getDouble(parent, title, label, value, minValue, maxValue, decimals, ok, flags); } + inline static QT3_SUPPORT QString getItem(const QString &title, const QString &label, const QStringList &list, + int current = 0, bool editable = true, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getItem(parent, title, label, list, current, editable, ok, flags); } +#endif + +Q_SIGNALS: + // ### emit signals! + void textValueChanged(const QString &text); + void textValueSelected(const QString &text); + void intValueChanged(int value); + void intValueSelected(int value); + void doubleValueChanged(double value); + void doubleValueSelected(double value); + + +public: + void done(int result); // ### Qt 5: Make protected. + +private: + Q_DISABLE_COPY(QInputDialog) + Q_PRIVATE_SLOT(d_func(), void _q_textChanged(const QString&)) + Q_PRIVATE_SLOT(d_func(), void _q_currentRowChanged(const QModelIndex&, const QModelIndex&)) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QInputDialog::InputDialogOptions) + +#endif // QT_NO_INPUTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QINPUTDIALOG_H diff --git a/src/widgets/dialogs/qmessagebox.cpp b/src/widgets/dialogs/qmessagebox.cpp new file mode 100644 index 0000000000..26180ab38b --- /dev/null +++ b/src/widgets/dialogs/qmessagebox.cpp @@ -0,0 +1,2753 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets/qmessagebox.h> + +#ifndef QT_NO_MESSAGEBOX + +#include <QtWidgets/qdialogbuttonbox.h> +#include "private/qlabel_p.h" +#include "private/qapplication_p.h" +#include <QtCore/qlist.h> +#include <QtCore/qdebug.h> +#include <QtWidgets/qstyle.h> +#include <QtWidgets/qstyleoption.h> +#include <QtWidgets/qgridlayout.h> +#include <QtWidgets/qdesktopwidget.h> +#include <QtWidgets/qpushbutton.h> +#include <QtWidgets/qaccessible.h> +#include <QtWidgets/qicon.h> +#include <QtGui/qtextdocument.h> +#include <QtWidgets/qapplication.h> +#include <QtWidgets/qtextedit.h> +#include <QtWidgets/qtextbrowser.h> +#include <QtWidgets/qmenu.h> +#include "qdialog_p.h" +#include <QtGui/qfont.h> +#include <QtGui/qfontmetrics.h> +#include <QtGui/qclipboard.h> + +#ifndef QT_NO_STYLE_S60 +#include <qs60style.h> +#endif + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_smartphone();//defined in qguifunctions_wince.cpp +extern bool qt_wince_is_pocket_pc(); //defined in qguifunctions_wince.cpp + +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +enum Button { Old_Ok = 1, Old_Cancel = 2, Old_Yes = 3, Old_No = 4, Old_Abort = 5, Old_Retry = 6, + Old_Ignore = 7, Old_YesAll = 8, Old_NoAll = 9, Old_ButtonMask = 0xFF, + NewButtonMask = 0xFFFFFC00 }; + +enum DetailButtonLabel { ShowLabel = 0, HideLabel = 1 }; +#ifndef QT_NO_TEXTEDIT +class QMessageBoxDetailsText : public QWidget +{ +public: + class TextEdit : public QTextEdit + { + public: + TextEdit(QWidget *parent=0) : QTextEdit(parent) { } + void contextMenuEvent(QContextMenuEvent * e) + { +#ifndef QT_NO_CONTEXTMENU + QMenu *menu = createStandardContextMenu(); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(e->globalPos()); +#else + Q_UNUSED(e); +#endif + } + }; + + QMessageBoxDetailsText(QWidget *parent=0) + : QWidget(parent) + { + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + textEdit = new TextEdit(); + textEdit->setFixedHeight(100); + textEdit->setFocusPolicy(Qt::NoFocus); + textEdit->setReadOnly(true); + layout->addWidget(textEdit); + setLayout(layout); + } + void setText(const QString &text) { textEdit->setPlainText(text); } + QString text() const { return textEdit->toPlainText(); } +private: + TextEdit *textEdit; +}; +#endif // QT_NO_TEXTEDIT + +class DetailButton : public QPushButton +{ +public: + DetailButton(QWidget *parent) : QPushButton(label(ShowLabel), parent) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + QString label(DetailButtonLabel label) const + { return label == ShowLabel ? QMessageBox::tr("Show Details...") : QMessageBox::tr("Hide Details..."); } + + void setLabel(DetailButtonLabel lbl) + { setText(label(lbl)); } + + QSize sizeHint() const + { + ensurePolished(); + QStyleOptionButton opt; + initStyleOption(&opt); + const QFontMetrics fm = fontMetrics(); + opt.text = label(ShowLabel); + QSize sz = fm.size(Qt::TextShowMnemonic, opt.text); + QSize ret = style()->sizeFromContents(QStyle::CT_PushButton, &opt, sz, this). + expandedTo(QApplication::globalStrut()); + opt.text = label(HideLabel); + sz = fm.size(Qt::TextShowMnemonic, opt.text); + ret.expandedTo(style()->sizeFromContents(QStyle::CT_PushButton, &opt, sz, this). + expandedTo(QApplication::globalStrut())); + return ret; + } +}; + + +class QMessageBoxPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QMessageBox) + +public: + QMessageBoxPrivate() : escapeButton(0), defaultButton(0), clickedButton(0), detailsButton(0), +#ifndef QT_NO_TEXTEDIT + detailsText(0), +#endif + compatMode(false), autoAddOkButton(true), + detectedEscapeButton(0), informativeLabel(0) { } + + void init(const QString &title = QString(), const QString &text = QString()); + void _q_buttonClicked(QAbstractButton *); + + QAbstractButton *findButton(int button0, int button1, int button2, int flags); + void addOldButtons(int button0, int button1, int button2); + + QAbstractButton *abstractButtonForId(int id) const; + int execReturnCode(QAbstractButton *button); + + void detectEscapeButton(); + void updateSize(); + int layoutMinimumWidth(); + void retranslateStrings(); + +#ifdef Q_WS_WINCE + void hideSpecial(); +#endif + + static int showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + int button0, int button1, int button2); + static int showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + const QString &button0Text, + const QString &button1Text, + const QString &button2Text, + int defaultButtonNumber, + int escapeButtonNumber); + + static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, + QMessageBox::Icon icon, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); + + static QPixmap standardIcon(QMessageBox::Icon icon, QMessageBox *mb); + + QLabel *label; + QMessageBox::Icon icon; + QLabel *iconLabel; + QDialogButtonBox *buttonBox; + QList<QAbstractButton *> customButtonList; + QAbstractButton *escapeButton; + QPushButton *defaultButton; + QAbstractButton *clickedButton; + DetailButton *detailsButton; +#ifndef QT_NO_TEXTEDIT + QMessageBoxDetailsText *detailsText; +#endif + bool compatMode; + bool autoAddOkButton; + QAbstractButton *detectedEscapeButton; + QLabel *informativeLabel; +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + QTextBrowser *textBrowser; +#endif + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + QByteArray signalToDisconnectOnClose; +}; + +void QMessageBoxPrivate::init(const QString &title, const QString &text) +{ + Q_Q(QMessageBox); + + label = new QLabel; + label->setObjectName(QLatin1String("qt_msgbox_label")); + label->setTextInteractionFlags(Qt::TextInteractionFlags(q->style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, q))); + label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + label->setOpenExternalLinks(true); +#if defined(Q_WS_MAC) + label->setContentsMargins(16, 0, 0, 0); +#elif !defined(Q_WS_QWS) + label->setContentsMargins(2, 0, 0, 0); + label->setIndent(9); +#endif + icon = QMessageBox::NoIcon; + iconLabel = new QLabel; + iconLabel->setObjectName(QLatin1String("qt_msgboxex_icon_label")); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + buttonBox = new QDialogButtonBox; + buttonBox->setObjectName(QLatin1String("qt_msgbox_buttonbox")); + buttonBox->setCenterButtons(q->style()->styleHint(QStyle::SH_MessageBox_CenterButtons, 0, q)); + QObject::connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), + q, SLOT(_q_buttonClicked(QAbstractButton*))); + + QGridLayout *grid = new QGridLayout; +#ifndef Q_WS_MAC + grid->addWidget(iconLabel, 0, 0, 2, 1, Qt::AlignTop); + grid->addWidget(label, 0, 1, 1, 1); + // -- leave space for information label -- + grid->addWidget(buttonBox, 2, 0, 1, 2); +#else + grid->setMargin(0); + grid->setVerticalSpacing(8); + grid->setHorizontalSpacing(0); + q->setContentsMargins(24, 15, 24, 20); + grid->addWidget(iconLabel, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignLeft); + grid->addWidget(label, 0, 1, 1, 1); + // -- leave space for information label -- + grid->setRowStretch(1, 100); + grid->setRowMinimumHeight(2, 6); + grid->addWidget(buttonBox, 3, 1, 1, 1); +#endif + + grid->setSizeConstraint(QLayout::SetNoConstraint); + q->setLayout(grid); + + if (!title.isEmpty() || !text.isEmpty()) { + q->setWindowTitle(title); + q->setText(text); + } + q->setModal(true); + +#ifdef Q_WS_MAC + QFont f = q->font(); + f.setBold(true); + label->setFont(f); +#endif + retranslateStrings(); +} + +int QMessageBoxPrivate::layoutMinimumWidth() +{ + layout->activate(); + return layout->totalMinimumSize().width(); +} + +void QMessageBoxPrivate::updateSize() +{ + Q_Q(QMessageBox); + + if (!q->isVisible()) + return; + + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); +#if defined(Q_WS_QWS) || defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) + // the width of the screen, less the window border. + int hardLimit = screenSize.width() - (q->frameGeometry().width() - q->geometry().width()); +#else + int hardLimit = qMin(screenSize.width() - 480, 1000); // can never get bigger than this + // on small screens allows the messagebox be the same size as the screen + if (screenSize.width() <= 1024) + hardLimit = screenSize.width(); +#endif +#ifdef Q_WS_MAC + int softLimit = qMin(screenSize.width()/2, 420); +#elif defined(Q_WS_QWS) + int softLimit = qMin(hardLimit, 500); +#else + // note: ideally on windows, hard and soft limits but it breaks compat +#ifndef Q_WS_WINCE + int softLimit = qMin(screenSize.width()/2, 500); +#else + int softLimit = qMin(screenSize.width() * 3 / 4, 500); +#endif //Q_WS_WINCE +#endif + + if (informativeLabel) + informativeLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + + label->setWordWrap(false); // makes the label return min size + int width = layoutMinimumWidth(); + + if (width > softLimit) { + label->setWordWrap(true); + width = qMax(softLimit, layoutMinimumWidth()); + + if (width > hardLimit) { + label->d_func()->ensureTextControl(); + if (QWidgetTextControl *control = label->d_func()->control) { + QTextOption opt = control->document()->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAnywhere); + control->document()->setDefaultTextOption(opt); + } + width = hardLimit; + } + } +#ifdef Q_WS_S60 + // in S60 portait messageBoxes should always occupy maximum width + if (QApplication::desktop()->size().height() > QApplication::desktop()->size().width()){ + width = hardLimit; + } else { + // in landscape the messageBoxes should be of same width as in portrait + width = qMin(QApplication::desktop()->size().height(), hardLimit); + } +#endif + + if (informativeLabel) { + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + QSizePolicy policy(QSizePolicy::Minimum, QSizePolicy::Preferred); + policy.setHeightForWidth(true); + informativeLabel->setSizePolicy(policy); + width = qMax(width, layoutMinimumWidth()); + if (width > hardLimit) { // longest word is really big, so wrap anywhere + informativeLabel->d_func()->ensureTextControl(); + if (QWidgetTextControl *control = informativeLabel->d_func()->control) { + QTextOption opt = control->document()->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAnywhere); + control->document()->setDefaultTextOption(opt); + } + width = hardLimit; + } + policy.setHeightForWidth(label->wordWrap()); + label->setSizePolicy(policy); + } + + QFontMetrics fm(QApplication::font("QWorkspaceTitleBar")); + int windowTitleWidth = qMin(fm.width(q->windowTitle()) + 50, hardLimit); + if (windowTitleWidth > width) + width = windowTitleWidth; + + layout->activate(); + int height = (layout->hasHeightForWidth()) + ? layout->totalHeightForWidth(width) + : layout->totalMinimumSize().height(); + +#ifndef QT_NO_STYLE_S60 + QS60Style *s60Style = 0; + s60Style = qobject_cast<QS60Style *>(QApplication::style()); + + //use custom pixel metric to deduce the minimum height of the messagebox + if (s60Style) + height = qMax(height, s60Style->pixelMetric((QStyle::PixelMetric)PM_MessageBoxHeight)); +#endif + + q->setFixedSize(width, height); + QCoreApplication::removePostedEvents(q, QEvent::LayoutRequest); +} + + +#ifdef Q_WS_WINCE +/*! + \internal + Hides special buttons which are rather shown in the title bar + on WinCE, to conserve screen space. +*/ + +void QMessageBoxPrivate::hideSpecial() +{ + Q_Q(QMessageBox); + QList<QPushButton*> list = q->findChildren<QPushButton*>(); + for (int i=0; i<list.size(); ++i) { + QPushButton *pb = list.at(i); + QString text = pb->text(); + text.remove(QChar::fromLatin1('&')); + if (text == QApplication::translate("QMessageBox", "OK" )) + pb->setFixedSize(0,0); + } +} +#endif + +static int oldButton(int button) +{ + switch (button & QMessageBox::ButtonMask) { + case QMessageBox::Ok: + return Old_Ok; + case QMessageBox::Cancel: + return Old_Cancel; + case QMessageBox::Yes: + return Old_Yes; + case QMessageBox::No: + return Old_No; + case QMessageBox::Abort: + return Old_Abort; + case QMessageBox::Retry: + return Old_Retry; + case QMessageBox::Ignore: + return Old_Ignore; + case QMessageBox::YesToAll: + return Old_YesAll; + case QMessageBox::NoToAll: + return Old_NoAll; + default: + return 0; + } +} + +int QMessageBoxPrivate::execReturnCode(QAbstractButton *button) +{ + int ret = buttonBox->standardButton(button); + if (ret == QMessageBox::NoButton) { + ret = customButtonList.indexOf(button); // if button == 0, correctly sets ret = -1 + } else if (compatMode) { + ret = oldButton(ret); + } + return ret; +} + +void QMessageBoxPrivate::_q_buttonClicked(QAbstractButton *button) +{ + Q_Q(QMessageBox); +#ifndef QT_NO_TEXTEDIT + if (detailsButton && detailsText && button == detailsButton) { + detailsButton->setLabel(detailsText->isHidden() ? HideLabel : ShowLabel); + detailsText->setHidden(!detailsText->isHidden()); + updateSize(); + } else +#endif + { + clickedButton = button; + q->done(execReturnCode(button)); // does not trigger closeEvent + emit q->buttonClicked(button); + + if (receiverToDisconnectOnClose) { + QObject::disconnect(q, signalToDisconnectOnClose, receiverToDisconnectOnClose, + memberToDisconnectOnClose); + receiverToDisconnectOnClose = 0; + } + signalToDisconnectOnClose.clear(); + memberToDisconnectOnClose.clear(); + } +} + +/*! + \class QMessageBox + + \brief The QMessageBox class provides a modal dialog for informing + the user or for asking the user a question and receiving an answer. + + \ingroup standard-dialogs + + + A message box displays a primary \l{QMessageBox::text}{text} to + alert the user to a situation, an \l{QMessageBox::informativeText} + {informative text} to further explain the alert or to ask the user + a question, and an optional \l{QMessageBox::detailedText} + {detailed text} to provide even more data if the user requests + it. A message box can also display an \l{QMessageBox::icon} {icon} + and \l{QMessageBox::standardButtons} {standard buttons} for + accepting a user response. + + Two APIs for using QMessageBox are provided, the property-based + API, and the static functions. Calling one of the static functions + is the simpler approach, but it is less flexible than using the + property-based API, and the result is less informative. Using the + property-based API is recommended. + + \section1 The Property-based API + + To use the property-based API, construct an instance of + QMessageBox, set the desired properties, and call exec() to show + the message. The simplest configuration is to set only the + \l{QMessageBox::text} {message text} property. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 5 + + The user must click the \gui{OK} button to dismiss the message + box. The rest of the GUI is blocked until the message box is + dismissed. + + \image msgbox1.png + + A better approach than just alerting the user to an event is to + also ask the user what to do about it. Store the question in the + \l{QMessageBox::informativeText} {informative text} property, and + set the \l{QMessageBox::standardButtons} {standard buttons} + property to the set of buttons you want as the set of user + responses. The buttons are specified by combining values from + StandardButtons using the bitwise OR operator. The display order + for the buttons is platform-dependent. For example, on Windows, + \gui{Save} is displayed to the left of \gui{Cancel}, whereas on + Mac OS, the order is reversed. + + Mark one of your standard buttons to be your + \l{QMessageBox::defaultButton()} {default button}. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 6 + + This is the approach recommended in the + \l{http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGWindows/chapter_18_section_7.html} + {Mac OS X Guidlines}. Similar guidlines apply for the other + platforms, but note the different ways the + \l{QMessageBox::informativeText} {informative text} is handled for + different platforms. + + \image msgbox2.png + + The exec() slot returns the StandardButtons value of the button + that was clicked. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 7 + + To give the user more information to help him answer the question, + set the \l{QMessageBox::detailedText} {detailed text} property. If + the \l{QMessageBox::detailedText} {detailed text} property is set, + the \gui{Show Details...} button will be shown. + + \image msgbox3.png + + Clicking the \gui{Show Details...} button displays the detailed text. + + \image msgbox4.png + + \section2 Rich Text and the Text Format Property + + The \l{QMessageBox::detailedText} {detailed text} property is + always interpreted as plain text. The \l{QMessageBox::text} {main + text} and \l{QMessageBox::informativeText} {informative text} + properties can be either plain text or rich text. These strings + are interpreted according to the setting of the + \l{QMessageBox::textFormat} {text format} property. The default + setting is \l{Qt::AutoText} {auto-text}. + + Note that for some plain text strings containing XML + meta-characters, the auto-text \l{Qt::mightBeRichText()} {rich + text detection test} may fail causing your plain text string to be + interpreted incorrectly as rich text. In these rare cases, use + Qt::convertFromPlainText() to convert your plain text string to a + visually equivalent rich text string, or set the + \l{QMessageBox::textFormat} {text format} property explicitly with + setTextFormat(). + + \section2 Severity Levels and the Icon and Pixmap Properties + + QMessageBox supports four predefined message severity levels, or message + types, which really only differ in the predefined icon they each show. + Specify one of the four predefined message types by setting the + \l{QMessageBox::icon}{icon} property to one of the + \l{QMessageBox::Icon}{predefined icons}. The following rules are + guidelines: + + \table + \row + \o \img qmessagebox-quest.png + \o \l Question + \o For asking a question during normal operations. + \row + \o \img qmessagebox-info.png + \o \l Information + \o For reporting information about normal operations. + \row + \o \img qmessagebox-warn.png + \o \l Warning + \o For reporting non-critical errors. + \row + \o \img qmessagebox-crit.png + \o \l Critical + \o For reporting critical errors. + \endtable + + \l{QMessageBox::Icon}{Predefined icons} are not defined by QMessageBox, but + provided by the style. The default value is \l{QMessageBox::NoIcon} + {No Icon}. The message boxes are otherwise the same for all cases. When + using a standard icon, use the one recommended in the table, or use the + one recommended by the style guidelines for your platform. If none of the + standard icons is right for your message box, you can use a custom icon by + setting the \l{QMessageBox::iconPixmap}{icon pixmap} property instead of + setting the \l{QMessageBox::icon}{icon} property. + + In summary, to set an icon, use \e{either} setIcon() for one of the + standard icons, \e{or} setIconPixmap() for a custom icon. + + \section1 The Static Functions API + + Building message boxes with the static functions API, although + convenient, is less flexible than using the property-based API, + because the static function signatures lack parameters for setting + the \l{QMessageBox::informativeText} {informative text} and + \l{QMessageBox::detailedText} {detailed text} properties. One + work-around for this has been to use the \c{title} parameter as + the message box main text and the \c{text} parameter as the + message box informative text. Because this has the obvious + drawback of making a less readable message box, platform + guidelines do not recommend it. The \e{Microsoft Windows User + Interface Guidelines} recommend using the + \l{QCoreApplication::applicationName} {application name} as the + \l{QMessageBox::setWindowTitle()} {window's title}, which means + that if you have an informative text in addition to your main + text, you must concatenate it to the \c{text} parameter. + + Note that the static function signatures have changed with respect + to their button parameters, which are now used to set the + \l{QMessageBox::standardButtons} {standard buttons} and the + \l{QMessageBox::defaultButton()} {default button}. + + Static functions are available for creating information(), + question(), warning(), and critical() message boxes. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 0 + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QMessageBox and the other built-in Qt dialogs. + + \section1 Advanced Usage + + If the \l{QMessageBox::StandardButtons} {standard buttons} are not + flexible enough for your message box, you can use the addButton() + overload that takes a text and a ButtonRoleto to add custom + buttons. The ButtonRole is used by QMessageBox to determine the + ordering of the buttons on screen (which varies according to the + platform). You can test the value of clickedButton() after calling + exec(). For example, + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 2 + + \section1 Default and Escape Keys + + The default button (i.e., the button activated when \key Enter is + pressed) can be specified using setDefaultButton(). If a default + button is not specified, QMessageBox tries to find one based on + the \l{ButtonRole} {button roles} of the buttons used in the + message box. + + The escape button (the button activated when \key Esc is pressed) + can be specified using setEscapeButton(). If an escape button is + not specified, QMessageBox tries to find one using these rules: + + \list 1 + + \o If there is only one button, it is the button activated when + \key Esc is pressed. + + \o If there is a \l Cancel button, it is the button activated when + \key Esc is pressed. + + \o If there is exactly one button having either + \l{QMessageBox::RejectRole} {the Reject role} or the + \l{QMessageBox::NoRole} {the No role}, it is the button + activated when \key Esc is pressed. + + \endlist + + When an escape button can't be determined using these rules, + pressing \key Esc has no effect. + + \sa QDialogButtonBox, {fowler}{GUI Design Handbook: Message Box}, {Standard Dialogs Example}, {Application Example} +*/ + +/*! + \enum QMessageBox::StandardButton + \since 4.2 + + These enums describe flags for standard buttons. Each button has a + defined \l ButtonRole. + + \value Ok An "OK" button defined with the \l AcceptRole. + \value Open A "Open" button defined with the \l AcceptRole. + \value Save A "Save" button defined with the \l AcceptRole. + \value Cancel A "Cancel" button defined with the \l RejectRole. + \value Close A "Close" button defined with the \l RejectRole. + \value Discard A "Discard" or "Don't Save" button, depending on the platform, + defined with the \l DestructiveRole. + \value Apply An "Apply" button defined with the \l ApplyRole. + \value Reset A "Reset" button defined with the \l ResetRole. + \value RestoreDefaults A "Restore Defaults" button defined with the \l ResetRole. + \value Help A "Help" button defined with the \l HelpRole. + \value SaveAll A "Save All" button defined with the \l AcceptRole. + \value Yes A "Yes" button defined with the \l YesRole. + \value YesToAll A "Yes to All" button defined with the \l YesRole. + \value No A "No" button defined with the \l NoRole. + \value NoToAll A "No to All" button defined with the \l NoRole. + \value Abort An "Abort" button defined with the \l RejectRole. + \value Retry A "Retry" button defined with the \l AcceptRole. + \value Ignore An "Ignore" button defined with the \l AcceptRole. + + \value NoButton An invalid button. + + \omitvalue FirstButton + \omitvalue LastButton + + The following values are obsolete: + + \value YesAll Use YesToAll instead. + \value NoAll Use NoToAll instead. + \value Default Use the \c defaultButton argument of + information(), warning(), etc. instead, or call + setDefaultButton(). + \value Escape Call setEscapeButton() instead. + \value FlagMask + \value ButtonMask + + \sa ButtonRole, standardButtons +*/ + +/*! + \fn void QMessageBox::buttonClicked(QAbstractButton *button) + + This signal is emitted whenever a button is clicked inside the QMessageBox. + The button that was clicked in returned in \a button. +*/ + +/*! + Constructs a message box with no text and no buttons. \a parent is + passed to the QDialog constructor. + + On Mac OS X, if you want your message box to appear + as a Qt::Sheet of its \a parent, set the message box's + \l{setWindowModality()} {window modality} to Qt::WindowModal or use open(). + Otherwise, the message box will be a standard dialog. + +*/ +QMessageBox::QMessageBox(QWidget *parent) + : QDialog(*new QMessageBoxPrivate, parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(); +} + +/*! + Constructs a message box with the given \a icon, \a title, \a + text, and standard \a buttons. Standard or custom buttons can be + added at any time using addButton(). The \a parent and \a f + arguments are passed to the QDialog constructor. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + On Mac OS X, if \a parent is not 0 and you want your message box + to appear as a Qt::Sheet of that parent, set the message box's + \l{setWindowModality()} {window modality} to Qt::WindowModal + (default). Otherwise, the message box will be a standard dialog. + + \sa setWindowTitle(), setText(), setIcon(), setStandardButtons() +*/ +QMessageBox::QMessageBox(Icon icon, const QString &title, const QString &text, + StandardButtons buttons, QWidget *parent, + Qt::WindowFlags f) +: QDialog(*new QMessageBoxPrivate, parent, f | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(title, text); + setIcon(icon); + if (buttons != NoButton) + setStandardButtons(buttons); +} + +/*! + Destroys the message box. +*/ +QMessageBox::~QMessageBox() +{ +} + +/*! + \since 4.2 + + Adds the given \a button to the message box with the specified \a + role. + + \sa removeButton(), button(), setStandardButtons() +*/ +void QMessageBox::addButton(QAbstractButton *button, ButtonRole role) +{ + Q_D(QMessageBox); + if (!button) + return; + removeButton(button); + d->buttonBox->addButton(button, (QDialogButtonBox::ButtonRole)role); + d->customButtonList.append(button); + d->autoAddOkButton = false; +} + +/*! + \since 4.2 + \overload + + Creates a button with the given \a text, adds it to the message box for the + specified \a role, and returns it. +*/ +QPushButton *QMessageBox::addButton(const QString& text, ButtonRole role) +{ + Q_D(QMessageBox); + QPushButton *pushButton = new QPushButton(text); + addButton(pushButton, role); + d->updateSize(); + return pushButton; +} + +/*! + \since 4.2 + \overload + + Adds a standard \a button to the message box if it is valid to do so, and + returns the push button. + + \sa setStandardButtons() +*/ +QPushButton *QMessageBox::addButton(StandardButton button) +{ + Q_D(QMessageBox); + QPushButton *pushButton = d->buttonBox->addButton((QDialogButtonBox::StandardButton)button); + if (pushButton) + d->autoAddOkButton = false; + return pushButton; +} + +/*! + \since 4.2 + + Removes \a button from the button box without deleting it. + + \sa addButton(), setStandardButtons() +*/ +void QMessageBox::removeButton(QAbstractButton *button) +{ + Q_D(QMessageBox); + d->customButtonList.removeAll(button); + if (d->escapeButton == button) + d->escapeButton = 0; + if (d->defaultButton == button) + d->defaultButton = 0; + d->buttonBox->removeButton(button); + d->updateSize(); +} + +/*! + \property QMessageBox::standardButtons + \brief collection of standard buttons in the message box + \since 4.2 + + This property controls which standard buttons are used by the message box. + + By default, this property contains no standard buttons. + + \sa addButton() +*/ +void QMessageBox::setStandardButtons(StandardButtons buttons) +{ + Q_D(QMessageBox); + d->buttonBox->setStandardButtons(QDialogButtonBox::StandardButtons(int(buttons))); + + QList<QAbstractButton *> buttonList = d->buttonBox->buttons(); + if (!buttonList.contains(d->escapeButton)) + d->escapeButton = 0; + if (!buttonList.contains(d->defaultButton)) + d->defaultButton = 0; + d->autoAddOkButton = false; + d->updateSize(); +} + +QMessageBox::StandardButtons QMessageBox::standardButtons() const +{ + Q_D(const QMessageBox); + return QMessageBox::StandardButtons(int(d->buttonBox->standardButtons())); +} + +/*! + \since 4.2 + + Returns the standard button enum value corresponding to the given \a button, + or NoButton if the given \a button isn't a standard button. + + \sa button(), standardButtons() +*/ +QMessageBox::StandardButton QMessageBox::standardButton(QAbstractButton *button) const +{ + Q_D(const QMessageBox); + return (QMessageBox::StandardButton)d->buttonBox->standardButton(button); +} + +/*! + \since 4.2 + + Returns a pointer corresponding to the standard button \a which, + or 0 if the standard button doesn't exist in this message box. + + \sa standardButtons, standardButton() +*/ +QAbstractButton *QMessageBox::button(StandardButton which) const +{ + Q_D(const QMessageBox); + return d->buttonBox->button(QDialogButtonBox::StandardButton(which)); +} + +/*! + \since 4.2 + + Returns the button that is activated when escape is pressed. + + By default, QMessageBox attempts to automatically detect an + escape button as follows: + + \list 1 + \o If there is only one button, it is made the escape button. + \o If there is a \l Cancel button, it is made the escape button. + \o On Mac OS X only, if there is exactly one button with the role + QMessageBox::RejectRole, it is made the escape button. + \endlist + + When an escape button could not be automatically detected, pressing + \key Esc has no effect. + + \sa addButton() +*/ +QAbstractButton *QMessageBox::escapeButton() const +{ + Q_D(const QMessageBox); + return d->escapeButton; +} + +/*! + \since 4.2 + + Sets the button that gets activated when the \key Escape key is + pressed to \a button. + + \sa addButton(), clickedButton() +*/ +void QMessageBox::setEscapeButton(QAbstractButton *button) +{ + Q_D(QMessageBox); + if (d->buttonBox->buttons().contains(button)) + d->escapeButton = button; +} + +/*! + \since 4.3 + + Sets the buttons that gets activated when the \key Escape key is + pressed to \a button. + + \sa addButton(), clickedButton() +*/ +void QMessageBox::setEscapeButton(QMessageBox::StandardButton button) +{ + Q_D(QMessageBox); + setEscapeButton(d->buttonBox->button(QDialogButtonBox::StandardButton(button))); +} + +void QMessageBoxPrivate::detectEscapeButton() +{ + if (escapeButton) { // escape button explicitly set + detectedEscapeButton = escapeButton; + return; + } + + // Cancel button automatically becomes escape button + detectedEscapeButton = buttonBox->button(QDialogButtonBox::Cancel); + if (detectedEscapeButton) + return; + + // If there is only one button, make it the escape button + const QList<QAbstractButton *> buttons = buttonBox->buttons(); + if (buttons.count() == 1) { + detectedEscapeButton = buttons.first(); + return; + } + + // if the message box has one RejectRole button, make it the escape button + for (int i = 0; i < buttons.count(); i++) { + if (buttonBox->buttonRole(buttons.at(i)) == QDialogButtonBox::RejectRole) { + if (detectedEscapeButton) { // already detected! + detectedEscapeButton = 0; + break; + } + detectedEscapeButton = buttons.at(i); + } + } + if (detectedEscapeButton) + return; + + // if the message box has one NoRole button, make it the escape button + for (int i = 0; i < buttons.count(); i++) { + if (buttonBox->buttonRole(buttons.at(i)) == QDialogButtonBox::NoRole) { + if (detectedEscapeButton) { // already detected! + detectedEscapeButton = 0; + break; + } + detectedEscapeButton = buttons.at(i); + } + } +} + +/*! + \since 4.2 + + Returns the button that was clicked by the user, + or 0 if the user hit the \key Esc key and + no \l{setEscapeButton()}{escape button} was set. + + If exec() hasn't been called yet, returns 0. + + Example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 3 + + \sa standardButton(), button() +*/ +QAbstractButton *QMessageBox::clickedButton() const +{ + Q_D(const QMessageBox); + return d->clickedButton; +} + +/*! + \since 4.2 + + Returns the button that should be the message box's + \l{QPushButton::setDefault()}{default button}. Returns 0 + if no default button was set. + + \sa addButton(), QPushButton::setDefault() +*/ +QPushButton *QMessageBox::defaultButton() const +{ + Q_D(const QMessageBox); + return d->defaultButton; +} + +/*! + \since 4.2 + + Sets the message box's \l{QPushButton::setDefault()}{default button} + to \a button. + + \sa addButton(), QPushButton::setDefault() +*/ +void QMessageBox::setDefaultButton(QPushButton *button) +{ + Q_D(QMessageBox); + if (!d->buttonBox->buttons().contains(button)) + return; + d->defaultButton = button; + button->setDefault(true); + button->setFocus(); +} + +/*! + \since 4.3 + + Sets the message box's \l{QPushButton::setDefault()}{default button} + to \a button. + + \sa addButton(), QPushButton::setDefault() +*/ +void QMessageBox::setDefaultButton(QMessageBox::StandardButton button) +{ + Q_D(QMessageBox); + setDefaultButton(d->buttonBox->button(QDialogButtonBox::StandardButton(button))); +} + +/*! + \property QMessageBox::text + \brief the message box text to be displayed. + + The text will be interpreted either as a plain text or as rich text, + depending on the text format setting (\l QMessageBox::textFormat). + The default setting is Qt::AutoText, i.e., the message box will try + to auto-detect the format of the text. + + The default value of this property is an empty string. + + \sa textFormat, QMessageBox::informativeText, QMessageBox::detailedText +*/ +QString QMessageBox::text() const +{ + Q_D(const QMessageBox); + return d->label->text(); +} + +void QMessageBox::setText(const QString &text) +{ + Q_D(QMessageBox); + d->label->setText(text); + d->label->setWordWrap(d->label->textFormat() == Qt::RichText + || (d->label->textFormat() == Qt::AutoText && Qt::mightBeRichText(text))); + d->updateSize(); +} + +/*! + \enum QMessageBox::Icon + + This enum has the following values: + + \value NoIcon the message box does not have any icon. + + \value Question an icon indicating that + the message is asking a question. + + \value Information an icon indicating that + the message is nothing out of the ordinary. + + \value Warning an icon indicating that the + message is a warning, but can be dealt with. + + \value Critical an icon indicating that + the message represents a critical problem. + +*/ + +/*! + \property QMessageBox::icon + \brief the message box's icon + + The icon of the message box can be specified with one of the + values: + + \list + \o QMessageBox::NoIcon + \o QMessageBox::Question + \o QMessageBox::Information + \o QMessageBox::Warning + \o QMessageBox::Critical + \endlist + + The default is QMessageBox::NoIcon. + + The pixmap used to display the actual icon depends on the current + \l{QWidget::style()} {GUI style}. You can also set a custom pixmap + for the icon by setting the \l{QMessageBox::iconPixmap} {icon + pixmap} property. + + \sa iconPixmap +*/ +QMessageBox::Icon QMessageBox::icon() const +{ + Q_D(const QMessageBox); + return d->icon; +} + +void QMessageBox::setIcon(Icon icon) +{ + Q_D(QMessageBox); + setIconPixmap(QMessageBoxPrivate::standardIcon((QMessageBox::Icon)icon, + this)); + d->icon = icon; +} + +/*! + \property QMessageBox::iconPixmap + \brief the current icon + + The icon currently used by the message box. Note that it's often + hard to draw one pixmap that looks appropriate in all GUI styles; + you may want to supply a different pixmap for each platform. + + By default, this property is undefined. + + \sa icon +*/ +QPixmap QMessageBox::iconPixmap() const +{ + Q_D(const QMessageBox); + if (d->iconLabel && d->iconLabel->pixmap()) + return *d->iconLabel->pixmap(); + return QPixmap(); +} + +void QMessageBox::setIconPixmap(const QPixmap &pixmap) +{ + Q_D(QMessageBox); + d->iconLabel->setPixmap(pixmap); + d->updateSize(); + d->icon = NoIcon; +} + +/*! + \property QMessageBox::textFormat + \brief the format of the text displayed by the message box + + The current text format used by the message box. See the \l + Qt::TextFormat enum for an explanation of the possible options. + + The default format is Qt::AutoText. + + \sa setText() +*/ +Qt::TextFormat QMessageBox::textFormat() const +{ + Q_D(const QMessageBox); + return d->label->textFormat(); +} + +void QMessageBox::setTextFormat(Qt::TextFormat format) +{ + Q_D(QMessageBox); + d->label->setTextFormat(format); + d->label->setWordWrap(format == Qt::RichText + || (format == Qt::AutoText && Qt::mightBeRichText(d->label->text()))); + d->updateSize(); +} + +/*! + \reimp +*/ +bool QMessageBox::event(QEvent *e) +{ + bool result =QDialog::event(e); + switch (e->type()) { + case QEvent::LayoutRequest: + d_func()->updateSize(); + break; + case QEvent::LanguageChange: + d_func()->retranslateStrings(); + break; +#ifdef Q_WS_WINCE + case QEvent::OkRequest: + case QEvent::HelpRequest: { + QString bName = + (e->type() == QEvent::OkRequest) + ? QApplication::translate("QMessageBox", "OK") + : QApplication::translate("QMessageBox", "Help"); + QList<QPushButton*> list = findChildren<QPushButton*>(); + for (int i=0; i<list.size(); ++i) { + QPushButton *pb = list.at(i); + if (pb->text() == bName) { + if (pb->isEnabled()) + pb->click(); + return pb->isEnabled(); + } + } + } +#endif + default: + break; + } + return result; +} + +/*! + \reimp +*/ +void QMessageBox::resizeEvent(QResizeEvent *event) +{ + QDialog::resizeEvent(event); +} + +/*! + \reimp +*/ +void QMessageBox::closeEvent(QCloseEvent *e) +{ + Q_D(QMessageBox); + if (!d->detectedEscapeButton) { + e->ignore(); + return; + } + QDialog::closeEvent(e); + d->clickedButton = d->detectedEscapeButton; + setResult(d->execReturnCode(d->detectedEscapeButton)); +} + +/*! + \reimp +*/ +void QMessageBox::changeEvent(QEvent *ev) +{ + Q_D(QMessageBox); + switch (ev->type()) { + case QEvent::StyleChange: + { + if (d->icon != NoIcon) + setIcon(d->icon); + Qt::TextInteractionFlags flags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this)); + d->label->setTextInteractionFlags(flags); + d->buttonBox->setCenterButtons(style()->styleHint(QStyle::SH_MessageBox_CenterButtons, 0, this)); + if (d->informativeLabel) + d->informativeLabel->setTextInteractionFlags(flags); + // intentional fall through + } + case QEvent::FontChange: + case QEvent::ApplicationFontChange: +#ifdef Q_WS_MAC + { + QFont f = font(); + f.setBold(true); + d->label->setFont(f); + } +#endif + default: + break; + } + QDialog::changeEvent(ev); +} + +/*! + \reimp +*/ +void QMessageBox::keyPressEvent(QKeyEvent *e) +{ + Q_D(QMessageBox); + if (e->key() == Qt::Key_Escape +#ifdef Q_WS_MAC + || (e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) +#endif + ) { + if (d->detectedEscapeButton) { +#ifdef Q_WS_MAC + d->detectedEscapeButton->animateClick(); +#else + d->detectedEscapeButton->click(); +#endif + } + return; + } + +#if defined (Q_OS_WIN) && !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) + if (e == QKeySequence::Copy) { + QString separator = QString::fromLatin1("---------------------------\n"); + QString textToCopy = separator; + separator.prepend(QLatin1Char('\n')); + textToCopy += windowTitle() + separator; // title + textToCopy += d->label->text() + separator; // text + + if (d->informativeLabel) + textToCopy += d->informativeLabel->text() + separator; + + QString buttonTexts; + QList<QAbstractButton *> buttons = d->buttonBox->buttons(); + for (int i = 0; i < buttons.count(); i++) { + buttonTexts += buttons[i]->text() + QLatin1String(" "); + } + textToCopy += buttonTexts + separator; + + QApplication::clipboard()->setText(textToCopy); + return; + } +#endif //QT_NO_SHORTCUT QT_NO_CLIPBOARD Q_OS_WIN + +#ifndef QT_NO_SHORTCUT + if (!(e->modifiers() & Qt::AltModifier)) { + int key = e->key() & ~((int)Qt::MODIFIER_MASK|(int)Qt::UNICODE_ACCEL); + if (key) { + const QList<QAbstractButton *> buttons = d->buttonBox->buttons(); + for (int i = 0; i < buttons.count(); ++i) { + QAbstractButton *pb = buttons.at(i); + int acc = pb->shortcut() & ~((int)Qt::MODIFIER_MASK|(int)Qt::UNICODE_ACCEL); + if (acc == key) { + pb->animateClick(); + return; + } + } + } + } +#endif + QDialog::keyPressEvent(e); +} + +#ifdef Q_WS_WINCE +/*! + \reimp +*/ +void QMessageBox::setVisible(bool visible) +{ + Q_D(QMessageBox); + if (visible) + d->hideSpecial(); + QDialog::setVisible(visible); +} +#endif + + +/*! + \overload + + Opens the dialog and connects its finished() or buttonClicked() signal to + the slot specified by \a receiver and \a member. If the slot in \a member + has a pointer for its first parameter the connection is to buttonClicked(), + otherwise the connection is to finished(). + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QMessageBox::open(QObject *receiver, const char *member) +{ + Q_D(QMessageBox); + const char *signal = member && strchr(member, '*') ? SIGNAL(buttonClicked(QAbstractButton*)) + : SIGNAL(finished(int)); + connect(this, signal, receiver, member); + d->signalToDisconnectOnClose = signal; + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \since 4.5 + + Returns a list of all the buttons that have been added to the message box. + + \sa buttonRole(), addButton(), removeButton() +*/ +QList<QAbstractButton *> QMessageBox::buttons() const +{ + Q_D(const QMessageBox); + return d->buttonBox->buttons(); +} + +/*! + \since 4.5 + + Returns the button role for the specified \a button. This function returns + \l InvalidRole if \a button is 0 or has not been added to the message box. + + \sa buttons(), addButton() +*/ +QMessageBox::ButtonRole QMessageBox::buttonRole(QAbstractButton *button) const +{ + Q_D(const QMessageBox); + return QMessageBox::ButtonRole(d->buttonBox->buttonRole(button)); +} + +/*! + \reimp +*/ +void QMessageBox::showEvent(QShowEvent *e) +{ + Q_D(QMessageBox); + if (d->autoAddOkButton) { + addButton(Ok); +#if defined(Q_WS_WINCE) + d->hideSpecial(); +#endif + } + if (d->detailsButton) + addButton(d->detailsButton, QMessageBox::ActionRole); + d->detectEscapeButton(); + d->updateSize(); + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::Alert); +#endif +#ifdef Q_WS_WIN + HMENU systemMenu = GetSystemMenu((HWND)winId(), FALSE); + if (!d->detectedEscapeButton) { + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED); + } + else { + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED); + } +#endif + QDialog::showEvent(e); +} + + +static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, + QMessageBox::Icon icon, + const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) +{ + // necessary for source compatibility with Qt 4.0 and 4.1 + // handles (Yes, No) and (Yes|Default, No) + if (defaultButton && !(buttons & defaultButton)) + return (QMessageBox::StandardButton) + QMessageBoxPrivate::showOldMessageBox(parent, icon, title, + text, int(buttons), + int(defaultButton), 0); + + QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent); + QDialogButtonBox *buttonBox = msgBox.findChild<QDialogButtonBox*>(); + Q_ASSERT(buttonBox != 0); + + uint mask = QMessageBox::FirstButton; + while (mask <= QMessageBox::LastButton) { + uint sb = buttons & mask; + mask <<= 1; + if (!sb) + continue; + QPushButton *button = msgBox.addButton((QMessageBox::StandardButton)sb); + // Choose the first accept role as the default + if (msgBox.defaultButton()) + continue; + if ((defaultButton == QMessageBox::NoButton && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton))) + msgBox.setDefaultButton(button); + } + if (msgBox.exec() == -1) + return QMessageBox::Cancel; + return msgBox.standardButton(msgBox.clickedButton()); +} + +/*! + \since 4.2 + + Opens an information message box with the given \a title and + \a text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. + \a defaultButton specifies the button used when \key Enter is pressed. + \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal}{application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ +QMessageBox::StandardButton QMessageBox::information(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Information, title, text, buttons, + defaultButton); +} + + +/*! + \since 4.2 + + Opens a question message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +QMessageBox::StandardButton QMessageBox::question(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Question, title, text, buttons, defaultButton); +} + +/*! + \since 4.2 + + Opens a warning message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), information(), critical() +*/ +QMessageBox::StandardButton QMessageBox::warning(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Warning, title, text, buttons, defaultButton); +} + +/*! + \since 4.2 + + Opens a critical message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), information() +*/ +QMessageBox::StandardButton QMessageBox::critical(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Critical, title, text, buttons, defaultButton); +} + +/*! + Displays a simple about box with title \a title and text \a + text. The about box's parent is \a parent. + + about() looks for a suitable icon in four locations: + + \list 1 + \o It prefers \link QWidget::windowIcon() parent->icon() \endlink + if that exists. + \o If not, it tries the top-level widget containing \a parent. + \o If that fails, it tries the \link + QApplication::activeWindow() active window. \endlink + \o As a last resort it uses the Information icon. + \endlist + + The about box has a single button labelled "OK". On Mac OS X, the + about box is popped up as a modeless window; on other platforms, + it is currently application modal. + + \sa QWidget::windowIcon(), QApplication::activeWindow() +*/ +void QMessageBox::about(QWidget *parent, const QString &title, const QString &text) +{ +#ifdef Q_WS_MAC + static QPointer<QMessageBox> oldMsgBox; + + if (oldMsgBox && oldMsgBox->text() == text) { + oldMsgBox->show(); + oldMsgBox->raise(); + oldMsgBox->activateWindow(); + return; + } +#endif + + QMessageBox *msgBox = new QMessageBox(title, text, Information, 0, 0, 0, parent +#ifdef Q_WS_MAC + , Qt::WindowTitleHint | Qt::WindowSystemMenuHint +#endif + ); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + QIcon icon = msgBox->windowIcon(); + QSize size = icon.actualSize(QSize(64, 64)); + msgBox->setIconPixmap(icon.pixmap(size)); + + // should perhaps be a style hint +#ifdef Q_WS_MAC + oldMsgBox = msgBox; +#if 0 + // ### doesn't work until close button is enabled in title bar + msgBox->d_func()->autoAddOkButton = false; +#else + msgBox->d_func()->buttonBox->setCenterButtons(true); +#endif + msgBox->show(); +#else + msgBox->exec(); +#endif +} + +/*! + Displays a simple message box about Qt, with the given \a title + and centered over \a parent (if \a parent is not 0). The message + includes the version number of Qt being used by the application. + + This is useful for inclusion in the \gui Help menu of an application, + as shown in the \l{mainwindows/menus}{Menus} example. + + QApplication provides this functionality as a slot. + + On Mac OS X, the about box is popped up as a modeless window; on + other platforms, it is currently application modal. + + \sa QApplication::aboutQt() +*/ +void QMessageBox::aboutQt(QWidget *parent, const QString &title) +{ +#ifdef Q_WS_MAC + static QPointer<QMessageBox> oldMsgBox; + + if (oldMsgBox) { + oldMsgBox->show(); + oldMsgBox->raise(); + oldMsgBox->activateWindow(); + return; + } +#endif + + QString translatedTextAboutQtCaption; + translatedTextAboutQtCaption = QMessageBox::tr( + "<h3>About Qt</h3>" + "<p>This program uses Qt version %1.</p>" + ).arg(QLatin1String(QT_VERSION_STR)); + QString translatedTextAboutQtText; + translatedTextAboutQtText = QMessageBox::tr( + "<p>Qt is a C++ toolkit for cross-platform application " + "development.</p>" + "<p>Qt provides single-source portability across MS Windows, " + "Mac OS X, Linux, and all major commercial Unix variants. " + "Qt is also available for embedded devices as Qt for Embedded Linux " + "and Qt for Windows CE.</p>" + "<p>Qt is available under three different licensing options designed " + "to accommodate the needs of our various users.</p>" + "<p>Qt licensed under our commercial license agreement is appropriate " + "for development of proprietary/commercial software where you do not " + "want to share any source code with third parties or otherwise cannot " + "comply with the terms of the GNU LGPL version 2.1 or GNU GPL version " + "3.0.</p>" + "<p>Qt licensed under the GNU LGPL version 2.1 is appropriate for the " + "development of Qt applications (proprietary or open source) provided " + "you can comply with the terms and conditions of the GNU LGPL version " + "2.1.</p>" + "<p>Qt licensed under the GNU General Public License version 3.0 is " + "appropriate for the development of Qt applications where you wish to " + "use such applications in combination with software subject to the " + "terms of the GNU GPL version 3.0 or where you are otherwise willing " + "to comply with the terms of the GNU GPL version 3.0.</p>" + "<p>Please see <a href=\"http://qt.nokia.com/products/licensing\">qt.nokia.com/products/licensing</a> " + "for an overview of Qt licensing.</p>" + "<p>Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).</p>" + "<p>Qt is a Nokia product. See <a href=\"http://qt.nokia.com/\">qt.nokia.com</a> " + "for more information.</p>" + ); + QMessageBox *msgBox = new QMessageBox(parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setWindowTitle(title.isEmpty() ? tr("About Qt") : title); + msgBox->setText(translatedTextAboutQtCaption); + msgBox->setInformativeText(translatedTextAboutQtText); + + QPixmap pm(QLatin1String(":/trolltech/qmessagebox/images/qtlogo-64.png")); + if (!pm.isNull()) + msgBox->setIconPixmap(pm); +#if defined(Q_WS_WINCE) + msgBox->setDefaultButton(msgBox->addButton(QMessageBox::Ok)); +#endif + + // should perhaps be a style hint +#ifdef Q_WS_MAC + oldMsgBox = msgBox; +#if 0 + // ### doesn't work until close button is enabled in title bar + msgBox->d_func()->autoAddOkButton = false; +#else + msgBox->d_func()->buttonBox->setCenterButtons(true); +#endif + msgBox->show(); +#else + msgBox->exec(); +#endif +} + +/*! + \internal +*/ +QSize QMessageBox::sizeHint() const +{ + // ### Qt 5: remove + return QDialog::sizeHint(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Source and binary compatibility routines for 4.0 and 4.1 + +static QMessageBox::StandardButton newButton(int button) +{ + // this is needed for source compatibility with Qt 4.0 and 4.1 + if (button == QMessageBox::NoButton || (button & NewButtonMask)) + return QMessageBox::StandardButton(button & QMessageBox::ButtonMask); + +#if QT_VERSION < 0x050000 + // this is needed for binary compatibility with Qt 4.0 and 4.1 + switch (button & Old_ButtonMask) { + case Old_Ok: + return QMessageBox::Ok; + case Old_Cancel: + return QMessageBox::Cancel; + case Old_Yes: + return QMessageBox::Yes; + case Old_No: + return QMessageBox::No; + case Old_Abort: + return QMessageBox::Abort; + case Old_Retry: + return QMessageBox::Retry; + case Old_Ignore: + return QMessageBox::Ignore; + case Old_YesAll: + return QMessageBox::YesToAll; + case Old_NoAll: + return QMessageBox::NoToAll; + default: + return QMessageBox::NoButton; + } +#else + return QMessageBox::NoButton; +#endif +} + +static bool detectedCompat(int button0, int button1, int button2) +{ + if (button0 != 0 && !(button0 & NewButtonMask)) + return true; + if (button1 != 0 && !(button1 & NewButtonMask)) + return true; + if (button2 != 0 && !(button2 & NewButtonMask)) + return true; + return false; +} + +QAbstractButton *QMessageBoxPrivate::findButton(int button0, int button1, int button2, int flags) +{ + Q_Q(QMessageBox); + int button = 0; + + if (button0 & flags) { + button = button0; + } else if (button1 & flags) { + button = button1; + } else if (button2 & flags) { + button = button2; + } + return q->button(newButton(button)); +} + +void QMessageBoxPrivate::addOldButtons(int button0, int button1, int button2) +{ + Q_Q(QMessageBox); + q->addButton(newButton(button0)); + q->addButton(newButton(button1)); + q->addButton(newButton(button2)); + q->setDefaultButton( + static_cast<QPushButton *>(findButton(button0, button1, button2, QMessageBox::Default))); + q->setEscapeButton(findButton(button0, button1, button2, QMessageBox::Escape)); + compatMode = detectedCompat(button0, button1, button2); +} + +QAbstractButton *QMessageBoxPrivate::abstractButtonForId(int id) const +{ + Q_Q(const QMessageBox); + QAbstractButton *result = customButtonList.value(id); + if (result) + return result; + if (id & QMessageBox::FlagMask) // for compatibility with Qt 4.0/4.1 (even if it is silly) + return 0; + return q->button(newButton(id)); +} + +int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + int button0, int button1, int button2) +{ + QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent); + messageBox.d_func()->addOldButtons(button0, button1, button2); + return messageBox.exec(); +} + +int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + const QString &button0Text, + const QString &button1Text, + const QString &button2Text, + int defaultButtonNumber, + int escapeButtonNumber) +{ + QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent); + QString myButton0Text = button0Text; + if (myButton0Text.isEmpty()) + myButton0Text = QDialogButtonBox::tr("OK"); + messageBox.addButton(myButton0Text, QMessageBox::ActionRole); + if (!button1Text.isEmpty()) + messageBox.addButton(button1Text, QMessageBox::ActionRole); + if (!button2Text.isEmpty()) + messageBox.addButton(button2Text, QMessageBox::ActionRole); + + const QList<QAbstractButton *> &buttonList = messageBox.d_func()->customButtonList; + messageBox.setDefaultButton(static_cast<QPushButton *>(buttonList.value(defaultButtonNumber))); + messageBox.setEscapeButton(buttonList.value(escapeButtonNumber)); + + return messageBox.exec(); +} + +void QMessageBoxPrivate::retranslateStrings() +{ +#ifndef QT_NO_TEXTEDIT + if (detailsButton) + detailsButton->setLabel(detailsText->isHidden() ? ShowLabel : HideLabel); +#endif +} + +/*! + \obsolete + + Constructs a message box with a \a title, a \a text, an \a icon, + and up to three buttons. + + The \a icon must be one of the following: + \list + \o QMessageBox::NoIcon + \o QMessageBox::Question + \o QMessageBox::Information + \o QMessageBox::Warning + \o QMessageBox::Critical + \endlist + + Each button, \a button0, \a button1 and \a button2, can have one + of the following values: + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + Use QMessageBox::NoButton for the later parameters to have fewer + than three buttons in your message box. If you don't specify any + buttons at all, QMessageBox will provide an Ok button. + + One of the buttons can be OR-ed with the QMessageBox::Default + flag to make it the default button (clicked when Enter is + pressed). + + One of the buttons can be OR-ed with the QMessageBox::Escape flag + to make it the cancel or close button (clicked when \key Esc is + pressed). + + \snippet doc/src/snippets/dialogs/dialogs.cpp 2 + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + The \a parent and \a f arguments are passed to + the QDialog constructor. + + \sa setWindowTitle(), setText(), setIcon() +*/ +QMessageBox::QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, QWidget *parent, + Qt::WindowFlags f) + : QDialog(*new QMessageBoxPrivate, parent, + f /*| Qt::MSWindowsFixedSizeDialogHint #### */| Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(title, text); + setIcon(icon); + d->addOldButtons(button0, button1, button2); +} + +/*! + \obsolete + + Opens an information message box with the given \a title and the + \a text. The dialog may have up to three buttons. Each of the + buttons, \a button0, \a button1 and \a button2 may be set to one + of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ +int QMessageBox::information(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Information, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays an information message box with the given \a title and + \a text, as well as one, two or three buttons. Returns the index + of the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be + used. \a button1Text is the text of the second button, and is + optional. \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the escape button; pressing + \key Esc is the same as clicking this button. It defaults to -1; + supply 0, 1 or 2 to make pressing \key Esc equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ + +int QMessageBox::information(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Information, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + +/*! + \obsolete + + Opens a question message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the buttons, \a + button0, \a button1 and \a button2 may be set to one of the + following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Yes, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +int QMessageBox::question(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Question, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a question message box with the given \a title and \a + text, as well as one, two or three buttons. Returns the index of + the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional. + \a button2Text is the text of the third button, and is optional. + \a defaultButtonNumber (0, 1 or 2) is the index of the default + button; pressing Return or Enter is the same as clicking the + default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1 or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +int QMessageBox::question(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Question, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + + +/*! + \obsolete + + Opens a warning message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the button + parameters, \a button0, \a button1 and \a button2 may be set to + one of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok or QMessageBox::No or ...) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), critical() +*/ +int QMessageBox::warning(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Warning, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a warning message box with the given \a title and \a + text, as well as one, two, or three buttons. Returns the number + of the button that was clicked (0, 1, or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional, + and \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1, or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), critical() +*/ +int QMessageBox::warning(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Warning, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + +/*! + \obsolete + + Opens a critical message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the button + parameters, \a button0, \a button1 and \a button2 may be set to + one of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), warning() +*/ + +int QMessageBox::critical(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Critical, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a critical error message box with the given \a title and + \a text, as well as one, two, or three buttons. Returns the + number of the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional, + and \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1, or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), warning() +*/ +int QMessageBox::critical(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Critical, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + + +/*! + \obsolete + + Returns the text of the message box button \a button, or + an empty string if the message box does not contain the button. + + Use button() and QPushButton::text() instead. +*/ +QString QMessageBox::buttonText(int button) const +{ + Q_D(const QMessageBox); + + if (QAbstractButton *abstractButton = d->abstractButtonForId(button)) { + return abstractButton->text(); + } else if (d->buttonBox->buttons().isEmpty() && (button == Ok || button == Old_Ok)) { + // for compatibility with Qt 4.0/4.1 + return QDialogButtonBox::tr("OK"); + } + return QString(); +} + +/*! + \obsolete + + Sets the text of the message box button \a button to \a text. + Setting the text of a button that is not in the message box is + silently ignored. + + Use addButton() instead. +*/ +void QMessageBox::setButtonText(int button, const QString &text) +{ + Q_D(QMessageBox); + if (QAbstractButton *abstractButton = d->abstractButtonForId(button)) { + abstractButton->setText(text); + } else if (d->buttonBox->buttons().isEmpty() && (button == Ok || button == Old_Ok)) { + // for compatibility with Qt 4.0/4.1 + addButton(QMessageBox::Ok)->setText(text); + } +} + +#ifndef QT_NO_TEXTEDIT +/*! + \property QMessageBox::detailedText + \brief the text to be displayed in the details area. + \since 4.2 + + The text will be interpreted as a plain text. + + By default, this property contains an empty string. + + \sa QMessageBox::text, QMessageBox::informativeText +*/ +QString QMessageBox::detailedText() const +{ + Q_D(const QMessageBox); + return d->detailsText ? d->detailsText->text() : QString(); +} + +void QMessageBox::setDetailedText(const QString &text) +{ + Q_D(QMessageBox); + if (text.isEmpty()) { + delete d->detailsText; + d->detailsText = 0; + removeButton(d->detailsButton); + delete d->detailsButton; + d->detailsButton = 0; + return; + } + + if (!d->detailsText) { + d->detailsText = new QMessageBoxDetailsText(this); + QGridLayout* grid = qobject_cast<QGridLayout*>(layout()); + if (grid) + grid->addWidget(d->detailsText, grid->rowCount(), 0, 1, grid->columnCount()); + d->detailsText->hide(); + } + if (!d->detailsButton) + d->detailsButton = new DetailButton(this); + d->detailsText->setText(text); +} +#endif // QT_NO_TEXTEDIT + +/*! + \property QMessageBox::informativeText + + \brief the informative text that provides a fuller description for + the message + + \since 4.2 + + Infromative text can be used to expand upon the text() to give more + information to the user. On the Mac, this text appears in small + system font below the text(). On other platforms, it is simply + appended to the existing text. + + By default, this property contains an empty string. + + \sa QMessageBox::text, QMessageBox::detailedText +*/ +QString QMessageBox::informativeText() const +{ + Q_D(const QMessageBox); + return d->informativeLabel ? d->informativeLabel->text() : QString(); +} + +void QMessageBox::setInformativeText(const QString &text) +{ + Q_D(QMessageBox); + if (text.isEmpty()) { + layout()->removeWidget(d->informativeLabel); + delete d->informativeLabel; + d->informativeLabel = 0; +#ifndef Q_WS_MAC + d->label->setContentsMargins(2, 0, 0, 0); +#endif + d->updateSize(); + return; + } + + if (!d->informativeLabel) { + QLabel *label = new QLabel; + label->setObjectName(QLatin1String("qt_msgbox_informativelabel")); + label->setTextInteractionFlags(Qt::TextInteractionFlags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this))); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + label->setOpenExternalLinks(true); + label->setWordWrap(true); +#ifndef Q_WS_MAC + d->label->setContentsMargins(2, 0, 0, 0); + label->setContentsMargins(2, 0, 0, 6); + label->setIndent(9); +#else + label->setContentsMargins(16, 0, 0, 0); + // apply a smaller font the information label on the mac + label->setFont(qt_app_fonts_hash()->value("QTipLabel")); +#endif + label->setWordWrap(true); + QGridLayout *grid = static_cast<QGridLayout *>(layout()); +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + label->hide(); + QTextBrowser *textBrowser = new QTextBrowser(this); + textBrowser->setOpenExternalLinks(true); + grid->addWidget(textBrowser, 1, 1, 1, 1); + d->textBrowser = textBrowser; +#else + grid->addWidget(label, 1, 1, 1, 1); +#endif + d->informativeLabel = label; + } + d->informativeLabel->setText(text); + +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + //We need to put the informative label inside textBrowser to enable scrolling of long texts. + d->textBrowser->setText(d->informativeLabel->text()); +#endif + + d->updateSize(); +} + +/*! + \since 4.2 + + This function shadows QWidget::setWindowTitle(). + + Sets the title of the message box to \a title. On Mac OS X, + the window title is ignored (as required by the Mac OS X + Guidelines). +*/ +void QMessageBox::setWindowTitle(const QString &title) +{ + // Message boxes on the mac do not have a title +#ifndef Q_WS_MAC + QDialog::setWindowTitle(title); +#else + Q_UNUSED(title); +#endif +} + + +/*! + \since 4.2 + + This function shadows QWidget::setWindowModality(). + + Sets the modality of the message box to \a windowModality. + + On Mac OS X, if the modality is set to Qt::WindowModal and the message box + has a parent, then the message box will be a Qt::Sheet, otherwise the + message box will be a standard dialog. +*/ +void QMessageBox::setWindowModality(Qt::WindowModality windowModality) +{ + QDialog::setWindowModality(windowModality); + + if (parentWidget() && windowModality == Qt::WindowModal) + setParent(parentWidget(), Qt::Sheet); + else + setParent(parentWidget(), Qt::Dialog); + setDefaultButton(d_func()->defaultButton); +} + +#ifdef QT3_SUPPORT +/*! + \compat + + Constructs a message box with the given \a parent, \a name, and + window flags, \a f. + The window title is specified by \a title, and the message box + displays message text and an icon specified by \a text and \a icon. + + The buttons that the user can access to respond to the message are + defined by \a button0, \a button1, and \a button2. +*/ +QMessageBox::QMessageBox(const QString& title, + const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent, const char *name, + bool modal, Qt::WindowFlags f) + : QDialog(*new QMessageBoxPrivate, parent, + f | Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + setObjectName(QString::fromAscii(name)); + d->init(title, text); + d->addOldButtons(button0, button1, button2); + setModal(modal); + setIcon(icon); +} + +/*! + \compat + Constructs a message box with the given \a parent and \a name. +*/ +QMessageBox::QMessageBox(QWidget *parent, const char *name) + : QDialog(*new QMessageBoxPrivate, parent, + Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Returns the pixmap used for a standard icon. This + allows the pixmaps to be used in more complex message boxes. + \a icon specifies the required icon, e.g. QMessageBox::Information, + QMessageBox::Warning or QMessageBox::Critical. + + \a style is unused. +*/ + +QPixmap QMessageBox::standardIcon(Icon icon, Qt::GUIStyle style) +{ + Q_UNUSED(style); + return QMessageBox::standardIcon(icon); +} + +/*! + \fn int QMessageBox::message(const QString &title, const QString &text, + const QString &buttonText, QWidget *parent = 0, + const char *name = 0) + + Opens a modal message box with the given \a title and showing the + given \a text. The message box has a single button which has the + given \a buttonText (or tr("OK")). The message box is centred over + its \a parent and is called \a name. + + Use information(), warning(), question(), or critical() instead. + + \oldcode + QMessageBox::message(tr("My App"), tr("All occurrences replaced."), + tr("Close"), this); + \newcode + QMessageBox::information(this, tr("My App"), + tr("All occurrences replaced."), + QMessageBox::Close); + \endcode +*/ + +/*! + \fn bool QMessageBox::query(const QString &caption, + const QString& text, + const QString& yesButtonText, + const QString& noButtonText, + QWidget *parent, const char *name) + + \obsolete + + Queries the user using a modal message box with up to two buttons. + The message box has the given \a caption (although some window + managers don't show it), and shows the given \a text. The left + button has the \a yesButtonText (or tr("OK")), and the right button + has the \a noButtonText (or isn't shown). The message box is centred + over its \a parent and is called \a name. + + Use information(), question(), warning(), or critical() instead. +*/ + +#endif + +QPixmap QMessageBoxPrivate::standardIcon(QMessageBox::Icon icon, QMessageBox *mb) +{ + QStyle *style = mb ? mb->style() : QApplication::style(); + int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, mb); + QIcon tmpIcon; + switch (icon) { + case QMessageBox::Information: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, 0, mb); + break; + case QMessageBox::Warning: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, 0, mb); + break; + case QMessageBox::Critical: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, 0, mb); + break; + case QMessageBox::Question: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mb); + default: + break; + } + if (!tmpIcon.isNull()) + return tmpIcon.pixmap(iconSize, iconSize); + return QPixmap(); +} + +/*! + \obsolete + + Returns the pixmap used for a standard icon. This allows the + pixmaps to be used in more complex message boxes. \a icon + specifies the required icon, e.g. QMessageBox::Question, + QMessageBox::Information, QMessageBox::Warning or + QMessageBox::Critical. + + Call QStyle::standardIcon() with QStyle::SP_MessageBoxInformation etc. + instead. +*/ + +QPixmap QMessageBox::standardIcon(Icon icon) +{ + return QMessageBoxPrivate::standardIcon(icon, 0); +} + +/*! + \typedef QMessageBox::Button + \obsolete + + Use QMessageBox::StandardButton instead. +*/ + +/*! + \fn int QMessageBox::information(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::warning(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::critical(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::question(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \internal + + ### Needed for Qt 4 source compatibility +*/ + +/*! + \fn int QMessageBox::exec() + + Shows the message box as a \l{QDialog#Modal Dialogs}{modal dialog}, + blocking until the user closes it. + + When using a QMessageBox with standard buttons, this functions returns a + \l StandardButton value indicating the standard button that was clicked. + When using QMessageBox with custom buttons, this function returns an + opaque value; use clickedButton() to determine which button was clicked. + + Users cannot interact with any other window in the same + application until they close the dialog, either by clicking a + button or by using a mechanism provided by the window system. + + \sa show(), result() +*/ + +QT_END_NAMESPACE + +#include "moc_qmessagebox.cpp" + +#endif // QT_NO_MESSAGEBOX diff --git a/src/widgets/dialogs/qmessagebox.h b/src/widgets/dialogs/qmessagebox.h new file mode 100644 index 0000000000..b2c99d3881 --- /dev/null +++ b/src/widgets/dialogs/qmessagebox.h @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMESSAGEBOX_H +#define QMESSAGEBOX_H + +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MESSAGEBOX + +class QLabel; +class QMessageBoxPrivate; +class QAbstractButton; + +class Q_WIDGETS_EXPORT QMessageBox : public QDialog +{ + Q_OBJECT + Q_ENUMS(Icon) + Q_FLAGS(StandardButtons) + Q_PROPERTY(QString text READ text WRITE setText) + // ### Qt 5: Rename 'icon' 'standardIcon' and 'iconPixmap' 'icon' (and use QIcon?) + Q_PROPERTY(Icon icon READ icon WRITE setIcon) + Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) + Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) + Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons) +#ifndef QT_NO_TEXTEDIT + Q_PROPERTY(QString detailedText READ detailedText WRITE setDetailedText) +#endif + Q_PROPERTY(QString informativeText READ informativeText WRITE setInformativeText) + +public: + enum Icon { + NoIcon = 0, + Information = 1, + Warning = 2, + Critical = 3, + Question = 4 + }; + + enum ButtonRole { + // keep this in sync with QDialogButtonBox::ButtonRole + InvalidRole = -1, + AcceptRole, + RejectRole, + DestructiveRole, + ActionRole, + HelpRole, + YesRole, + NoRole, + ResetRole, + ApplyRole, + + NRoles + }; + + enum StandardButton { + // keep this in sync with QDialogButtonBox::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, + + FirstButton = Ok, // internal + LastButton = RestoreDefaults, // internal + + YesAll = YesToAll, // obsolete + NoAll = NoToAll, // obsolete + + Default = 0x00000100, // obsolete + Escape = 0x00000200, // obsolete + FlagMask = 0x00000300, // obsolete + ButtonMask = ~FlagMask // obsolete + }; + typedef StandardButton Button; // obsolete + + Q_DECLARE_FLAGS(StandardButtons, StandardButton) + + explicit QMessageBox(QWidget *parent = 0); + QMessageBox(Icon icon, const QString &title, const QString &text, + StandardButtons buttons = NoButton, QWidget *parent = 0, + Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + ~QMessageBox(); + + void addButton(QAbstractButton *button, ButtonRole role); + QPushButton *addButton(const QString &text, ButtonRole role); + QPushButton *addButton(StandardButton button); + void removeButton(QAbstractButton *button); + +#ifdef Q_WS_WINCE + void setVisible(bool visible); +#endif + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + QList<QAbstractButton *> buttons() const; + ButtonRole buttonRole(QAbstractButton *button) const; + + void setStandardButtons(StandardButtons buttons); + StandardButtons standardButtons() const; + StandardButton standardButton(QAbstractButton *button) const; + QAbstractButton *button(StandardButton which) const; + + QPushButton *defaultButton() const; + void setDefaultButton(QPushButton *button); + void setDefaultButton(StandardButton button); + + QAbstractButton *escapeButton() const; + void setEscapeButton(QAbstractButton *button); + void setEscapeButton(StandardButton button); + + QAbstractButton *clickedButton() const; + + QString text() const; + void setText(const QString &text); + + Icon icon() const; + void setIcon(Icon); + + QPixmap iconPixmap() const; + void setIconPixmap(const QPixmap &pixmap); + + Qt::TextFormat textFormat() const; + void setTextFormat(Qt::TextFormat format); + + static StandardButton information(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + // ### Qt 5: Replace Ok with Yes|No in question() function. + // Also consider if Ok == Yes and Cancel == No. + static StandardButton question(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static StandardButton warning(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static StandardButton critical(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static void about(QWidget *parent, const QString &title, const QString &text); + static void aboutQt(QWidget *parent, const QString &title = QString()); + + QSize sizeHint() const; + + // the following functions are obsolete: + + QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent = 0, + Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + static int information(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1 = 0, int button2 = 0); + static int information(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static StandardButton information(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1 = NoButton) + { return information(parent, title, text, StandardButtons(button0), button1); } + + static int question(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1 = 0, int button2 = 0); + static int question(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int question(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return question(parent, title, text, StandardButtons(button0), button1); } + + static int warning(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1, int button2 = 0); + static int warning(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int warning(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return warning(parent, title, text, StandardButtons(button0), button1); } + + static int critical(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1, int button2 = 0); + static int critical(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int critical(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return critical(parent, title, text, StandardButtons(button0), button1); } + + QString buttonText(int button) const; + void setButtonText(int button, const QString &text); + + QString informativeText() const; + void setInformativeText(const QString &text); + +#ifndef QT_NO_TEXTEDIT + QString detailedText() const; + void setDetailedText(const QString &text); +#endif + + void setWindowTitle(const QString &title); + void setWindowModality(Qt::WindowModality windowModality); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent, const char *name, bool modal, + Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + QT3_SUPPORT_CONSTRUCTOR QMessageBox(QWidget *parent, const char *name); + + static QT3_SUPPORT QPixmap standardIcon(Icon icon, Qt::GUIStyle); + static QT3_SUPPORT int message(const QString &title, + const QString& text, + const QString& buttonText=QString(), + QWidget *parent = 0, const char * = 0) { + return QMessageBox::information(parent, title, text, + buttonText.isEmpty() ? tr("OK") : buttonText) == 0; + } + static QT3_SUPPORT bool query(const QString &title, + const QString& text, + const QString& yesButtonText = QString(), + const QString& noButtonText = QString(), + QWidget *parent = 0, const char * = 0) { + return QMessageBox::information(parent, title, text, + yesButtonText.isEmpty() ? tr("OK") : yesButtonText, + noButtonText) == 0; + } +#endif + + static QPixmap standardIcon(Icon icon); + +Q_SIGNALS: + void buttonClicked(QAbstractButton *button); + +#ifdef qdoc +public Q_SLOTS: + int exec(); +#endif + +protected: + bool event(QEvent *e); + void resizeEvent(QResizeEvent *event); + void showEvent(QShowEvent *event); + void closeEvent(QCloseEvent *event); + void keyPressEvent(QKeyEvent *event); + void changeEvent(QEvent *event); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_buttonClicked(QAbstractButton *)) + + Q_DISABLE_COPY(QMessageBox) + Q_DECLARE_PRIVATE(QMessageBox) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMessageBox::StandardButtons) + +#define QT_REQUIRE_VERSION(argc, argv, str) { QString s = QString::fromLatin1(str);\ +QString sq = QString::fromLatin1(qVersion()); \ +if ((sq.section(QChar::fromLatin1('.'),0,0).toInt()<<16)+\ +(sq.section(QChar::fromLatin1('.'),1,1).toInt()<<8)+\ +sq.section(QChar::fromLatin1('.'),2,2).toInt()<(s.section(QChar::fromLatin1('.'),0,0).toInt()<<16)+\ +(s.section(QChar::fromLatin1('.'),1,1).toInt()<<8)+\ +s.section(QChar::fromLatin1('.'),2,2).toInt()) { \ +if (!qApp){ \ + new QApplication(argc,argv); \ +} \ +QString s = QApplication::tr("Executable '%1' requires Qt "\ + "%2, found Qt %3.").arg(qAppName()).arg(QString::fromLatin1(\ +str)).arg(QString::fromLatin1(qVersion())); QMessageBox::critical(0, QApplication::tr(\ +"Incompatible Qt Library Error"), s, QMessageBox::Abort, 0); qFatal("%s", s.toLatin1().data()); }} + +#endif // QT_NO_MESSAGEBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMESSAGEBOX_H diff --git a/src/widgets/dialogs/qmessagebox.qrc b/src/widgets/dialogs/qmessagebox.qrc new file mode 100644 index 0000000000..8e6d7af671 --- /dev/null +++ b/src/widgets/dialogs/qmessagebox.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/qmessagebox"> + <file>images/qtlogo-64.png</file> +</qresource> +</RCC> diff --git a/src/widgets/dialogs/qnspanelproxy_mac.mm b/src/widgets/dialogs/qnspanelproxy_mac.mm new file mode 100644 index 0000000000..1de548413a --- /dev/null +++ b/src/widgets/dialogs/qnspanelproxy_mac.mm @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdialogbuttonbox.h> +#if defined(Q_WS_MAC) +#include <private/qt_mac_p.h> +#include <private/qcocoaintrospection_p.h> +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> +#import <objc/objc-class.h> + +QT_BEGIN_NAMESPACE +static QWidget *currentWindow = 0; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QNSPanelProxy); + +@interface QT_MANGLE_NAMESPACE(QNSPanelProxy) : NSWindow { +} +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen; +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSPanelProxy) +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation +{ + // remove evil flag + windowStyle &= ~NSUtilityWindowMask; + self = [self qt_fakeInitWithContentRect:contentRect styleMask:windowStyle + backing:bufferingType defer:deferCreation]; + return self; +} + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen +{ + // remove evil flag + windowStyle &= ~NSUtilityWindowMask; + return [self qt_fakeInitWithContentRect:contentRect styleMask:windowStyle + backing:bufferingType defer:deferCreation screen:screen]; +} + +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation +{ + Q_UNUSED(contentRect); + Q_UNUSED(windowStyle); + Q_UNUSED(bufferingType); + Q_UNUSED(deferCreation); + return nil; +} + +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen +{ + Q_UNUSED(contentRect); + Q_UNUSED(windowStyle); + Q_UNUSED(bufferingType); + Q_UNUSED(deferCreation); + Q_UNUSED(screen); + return nil; +} +@end + +@class QT_MANGLE_NAMESPACE(QNSWindowProxy); + +@interface QT_MANGLE_NAMESPACE(QNSWindowProxy) : NSWindow { +} +- (void)setTitle:(NSString *)title; +- (void)qt_fakeSetTitle:(NSString *)title; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSWindowProxy) +- (void)setTitle:(NSString *)title +{ + QCFString cftitle(currentWindow->windowTitle()); + + // evil reverse engineering + if ([title isEqualToString:@"Print"] + || [title isEqualToString:@"Page Setup"] + || [[self className] isEqualToString:@"PMPrintingWindow"]) + title = (NSString *)(static_cast<CFStringRef>(cftitle)); + return [self qt_fakeSetTitle:title]; +} + +- (void)qt_fakeSetTitle:(NSString *)title +{ + Q_UNUSED(title); +} +@end + +QT_BEGIN_NAMESPACE + +/* + Intercept the NSColorPanel constructor if the shared + color panel doesn't exist yet. What's going on here is + quite wacky, because we want to override the NSPanel + constructor and at the same time call the old NSPanel + constructor. So what we do is we effectively rename the + old NSPanel constructor qt_fakeInitWithContentRect:... + and have the new one call the old one. +*/ +void macStartInterceptNSPanelCtor() +{ + qt_cocoa_change_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:), + [QT_MANGLE_NAMESPACE(QNSPanelProxy) class], + @selector(initWithContentRect:styleMask:backing:defer:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:)); + qt_cocoa_change_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + [QT_MANGLE_NAMESPACE(QNSPanelProxy) class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:screen:)); +} + +/* + Restore things as they were. +*/ +void macStopInterceptNSPanelCtor() +{ + qt_cocoa_change_back_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:screen:)); + qt_cocoa_change_back_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:)); +} + +/* + Intercept the NSPrintPanel and NSPageLayout setTitle: calls. The + hack is similar as for NSColorPanel above. +*/ +void macStartInterceptWindowTitle(QWidget *window) +{ + currentWindow = window; + qt_cocoa_change_implementation( + [NSWindow class], + @selector(setTitle:), + [QT_MANGLE_NAMESPACE(QNSWindowProxy) class], + @selector(setTitle:), + @selector(qt_fakeSetTitle:)); +} + +/* + Restore things as they were. +*/ +void macStopInterceptWindowTitle() +{ + currentWindow = 0; + qt_cocoa_change_back_implementation( + [NSWindow class], + @selector(setTitle:), + @selector(qt_fakeSetTitle:)); +} + +/* + Doesn't really belong in here. +*/ +NSButton *macCreateButton(const char *text, NSView *superview) +{ + static const NSRect buttonFrameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + + NSButton *button = [[NSButton alloc] initWithFrame:buttonFrameRect]; + [button setButtonType:NSMomentaryLightButton]; + [button setBezelStyle:NSRoundedBezelStyle]; + [button setTitle:(NSString*)(CFStringRef)QCFString(QDialogButtonBox::tr(text) + .remove(QLatin1Char('&')))]; + [[button cell] setFont:[NSFont systemFontOfSize: + [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + [superview addSubview:button]; + return button; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qprogressdialog.cpp b/src/widgets/dialogs/qprogressdialog.cpp new file mode 100644 index 0000000000..8701dc10fb --- /dev/null +++ b/src/widgets/dialogs/qprogressdialog.cpp @@ -0,0 +1,907 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprogressdialog.h" + +#ifndef QT_NO_PROGRESSDIALOG + +#include "qshortcut.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qlabel.h" +#include "qprogressbar.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qpushbutton.h" +#include "qcursor.h" +#include "qtimer.h" +#include "qelapsedtimer.h" +#include <private/qdialog_p.h> +#include <limits.h> + +#if defined(QT_SOFTKEYS_ENABLED) +#include <qaction.h> +#endif +#ifdef Q_WS_S60 +#include <QtWidgets/qdesktopwidget.h> +#endif + + +QT_BEGIN_NAMESPACE + +// If the operation is expected to take this long (as predicted by +// progress time), show the progress dialog. +static const int defaultShowTime = 4000; +// Wait at least this long before attempting to make a prediction. +static const int minWaitTime = 50; + +class QProgressDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QProgressDialog) + +public: + QProgressDialogPrivate() : label(0), cancel(0), bar(0), + shown_once(false), + cancellation_flag(false), + showTime(defaultShowTime), +#ifndef QT_NO_SHORTCUT + escapeShortcut(0), +#endif +#ifdef QT_SOFTKEYS_ENABLED + cancelAction(0), +#endif + useDefaultCancelText(false) + { + } + + void init(const QString &labelText, const QString &cancelText, int min, int max); + void layout(); + void retranslateStrings(); + void _q_disconnectOnClose(); + + QLabel *label; + QPushButton *cancel; + QProgressBar *bar; + QTimer *forceTimer; + bool shown_once; + bool cancellation_flag; + QElapsedTimer starttime; +#ifndef QT_NO_CURSOR + QCursor parentCursor; +#endif + int showTime; + bool autoClose; + bool autoReset; + bool forceHide; +#ifndef QT_NO_SHORTCUT + QShortcut *escapeShortcut; +#endif +#ifdef QT_SOFTKEYS_ENABLED + QAction *cancelAction; +#endif + bool useDefaultCancelText; + QPointer<QObject> receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +void QProgressDialogPrivate::init(const QString &labelText, const QString &cancelText, + int min, int max) +{ + Q_Q(QProgressDialog); + label = new QLabel(labelText, q); + int align = q->style()->styleHint(QStyle::SH_ProgressDialog_TextLabelAlignment, 0, q); + label->setAlignment(Qt::Alignment(align)); + bar = new QProgressBar(q); + bar->setRange(min, max); + autoClose = true; + autoReset = true; + forceHide = false; + QObject::connect(q, SIGNAL(canceled()), q, SLOT(cancel())); + forceTimer = new QTimer(q); + QObject::connect(forceTimer, SIGNAL(timeout()), q, SLOT(forceShow())); + if (useDefaultCancelText) { + retranslateStrings(); + } else { + q->setCancelButtonText(cancelText); + } +} + +void QProgressDialogPrivate::layout() +{ + Q_Q(QProgressDialog); + int sp = q->style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); + int mtb = q->style()->pixelMetric(QStyle::PM_DefaultTopLevelMargin); + int mlr = qMin(q->width() / 10, mtb); + const bool centered = + bool(q->style()->styleHint(QStyle::SH_ProgressDialog_CenterCancelButton, 0, q)); + + int additionalSpacing = 0; +#ifdef Q_OS_SYMBIAN + //In Symbian, we need to have wider margins for dialog borders, as the actual border is some pixels + //inside the dialog area (to enable transparent borders) + additionalSpacing = mlr; +#endif + + QSize cs = cancel ? cancel->sizeHint() : QSize(0,0); + QSize bh = bar->sizeHint(); + int cspc; + int lh = 0; + + // Find spacing and sizes that fit. It is important that a progress + // dialog can be made very small if the user demands it so. + for (int attempt=5; attempt--;) { + cspc = cancel ? cs.height() + sp : 0; + lh = qMax(0, q->height() - mtb - bh.height() - sp - cspc); + + if (lh < q->height()/4) { + // Getting cramped + sp /= 2; + mtb /= 2; + if (cancel) { + cs.setHeight(qMax(4,cs.height()-sp-2)); + } + bh.setHeight(qMax(4,bh.height()-sp-1)); + } else { + break; + } + } + + if (cancel) { + cancel->setGeometry( + centered ? q->width()/2 - cs.width()/2 : q->width() - mlr - cs.width(), + q->height() - mtb - cs.height(), + cs.width(), cs.height()); + } + + if (label) + label->setGeometry(mlr, additionalSpacing, q->width() - mlr * 2, lh); + bar->setGeometry(mlr, lh + sp + additionalSpacing, q->width() - mlr * 2, bh.height()); +} + +void QProgressDialogPrivate::retranslateStrings() +{ + Q_Q(QProgressDialog); + if (useDefaultCancelText) + q->setCancelButtonText(QProgressDialog::tr("Cancel")); +} + +void QProgressDialogPrivate::_q_disconnectOnClose() +{ + Q_Q(QProgressDialog); + if (receiverToDisconnectOnClose) { + QObject::disconnect(q, SIGNAL(canceled()), receiverToDisconnectOnClose, + memberToDisconnectOnClose); + receiverToDisconnectOnClose = 0; + } + memberToDisconnectOnClose.clear(); +} + +/*! + \class QProgressDialog + \brief The QProgressDialog class provides feedback on the progress of a slow operation. + \ingroup standard-dialogs + + + A progress dialog is used to give the user an indication of how long + an operation is going to take, and to demonstrate that the + application has not frozen. It can also give the user an opportunity + to abort the operation. + + A common problem with progress dialogs is that it is difficult to know + when to use them; operations take different amounts of time on different + hardware. QProgressDialog offers a solution to this problem: + it estimates the time the operation will take (based on time for + steps), and only shows itself if that estimate is beyond minimumDuration() + (4 seconds by default). + + Use setMinimum() and setMaximum() or the constructor to set the number of + "steps" in the operation and call setValue() as the operation + progresses. The number of steps can be chosen arbitrarily. It can be the + number of files copied, the number of bytes received, the number of + iterations through the main loop of your algorithm, or some other + suitable unit. Progress starts at the value set by setMinimum(), + and the progress dialog shows that the operation has finished when + you call setValue() with the value set by setMaximum() as its argument. + + The dialog automatically resets and hides itself at the end of the + operation. Use setAutoReset() and setAutoClose() to change this + behavior. Note that if you set a new maximum (using setMaximum() or + setRange()) that equals your current value(), the dialog will not + close regardless. + + There are two ways of using QProgressDialog: modal and modeless. + + Compared to a modeless QProgressDialog, a modal QProgressDialog is simpler + to use for the programmer. Do the operation in a loop, call \l setValue() at + intervals, and check for cancellation with wasCanceled(). For example: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 3 + + A modeless progress dialog is suitable for operations that take + place in the background, where the user is able to interact with the + application. Such operations are typically based on QTimer (or + QObject::timerEvent()), QSocketNotifier, or QUrlOperator; or performed + in a separate thread. A QProgressBar in the status bar of your main window + is often an alternative to a modeless progress dialog. + + You need to have an event loop to be running, connect the + canceled() signal to a slot that stops the operation, and call \l + setValue() at intervals. For example: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 4 + \codeline + \snippet doc/src/snippets/dialogs/dialogs.cpp 5 + \codeline + \snippet doc/src/snippets/dialogs/dialogs.cpp 6 + + In both modes the progress dialog may be customized by + replacing the child widgets with custom widgets by using setLabel(), + setBar(), and setCancelButton(). + The functions setLabelText() and setCancelButtonText() + set the texts shown. + + \image plastique-progressdialog.png A progress dialog shown in the Plastique widget style. + + \sa QDialog, QProgressBar, {fowler}{GUI Design Handbook: Progress Indicator}, + {Find Files Example}, {Pixelator Example} +*/ + + +/*! + Constructs a progress dialog. + + Default settings: + \list + \i The label text is empty. + \i The cancel button text is (translated) "Cancel". + \i minimum is 0; + \i maximum is 100 + \endlist + + The \a parent argument is dialog's parent widget. The widget flags, \a f, are + passed to the QDialog::QDialog() constructor. + + \sa setLabelText(), setCancelButtonText(), setCancelButton(), + setMinimum(), setMaximum() +*/ + +QProgressDialog::QProgressDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(*(new QProgressDialogPrivate), parent, f) +{ + Q_D(QProgressDialog); + d->useDefaultCancelText = true; + d->init(QString::fromLatin1(""), QString(), 0, 100); +} + +/*! + Constructs a progress dialog. + + The \a labelText is the text used to remind the user what is progressing. + + The \a cancelButtonText is the text to display on the cancel button. If + QString() is passed then no cancel button is shown. + + The \a minimum and \a maximum is the number of steps in the operation for + which this progress dialog shows progress. For example, if the + operation is to examine 50 files, this value minimum value would be 0, + and the maximum would be 50. Before examining the first file, call + setValue(0). As each file is processed call setValue(1), setValue(2), + etc., finally calling setValue(50) after examining the last file. + + The \a parent argument is the dialog's parent widget. The parent, \a parent, and + widget flags, \a f, are passed to the QDialog::QDialog() constructor. + + \sa setLabelText(), setLabel(), setCancelButtonText(), setCancelButton(), + setMinimum(), setMaximum() +*/ + +QProgressDialog::QProgressDialog(const QString &labelText, + const QString &cancelButtonText, + int minimum, int maximum, + QWidget *parent, Qt::WindowFlags f) + : QDialog(*(new QProgressDialogPrivate), parent, f) +{ + Q_D(QProgressDialog); + d->init(labelText, cancelButtonText, minimum, maximum); +} + + +/*! + Destroys the progress dialog. +*/ + +QProgressDialog::~QProgressDialog() +{ +} + +/*! + \fn void QProgressDialog::canceled() + + This signal is emitted when the cancel button is clicked. + It is connected to the cancel() slot by default. + + \sa wasCanceled() +*/ + + +/*! + Sets the label to \a label. The progress dialog resizes to fit. The + label becomes owned by the progress dialog and will be deleted when + necessary, so do not pass the address of an object on the stack. + + \sa setLabelText() +*/ + +void QProgressDialog::setLabel(QLabel *label) +{ + Q_D(QProgressDialog); + delete d->label; + d->label = label; + if (label) { + if (label->parentWidget() == this) { + label->hide(); // until we resize + } else { + label->setParent(this, 0); + } + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + if (label) + label->show(); +} + + +/*! + \property QProgressDialog::labelText + \brief the label's text + + The default text is an empty string. +*/ + +QString QProgressDialog::labelText() const +{ + Q_D(const QProgressDialog); + if (d->label) + return d->label->text(); + return QString(); +} + +void QProgressDialog::setLabelText(const QString &text) +{ + Q_D(QProgressDialog); + if (d->label) { + d->label->setText(text); + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + } +} + + +/*! + Sets the cancel button to the push button, \a cancelButton. The + progress dialog takes ownership of this button which will be deleted + when necessary, so do not pass the address of an object that is on + the stack, i.e. use new() to create the button. If 0 is passed then + no cancel button will be shown. + + \sa setCancelButtonText() +*/ + +void QProgressDialog::setCancelButton(QPushButton *cancelButton) +{ + Q_D(QProgressDialog); + delete d->cancel; + d->cancel = cancelButton; + if (cancelButton) { + if (cancelButton->parentWidget() == this) { + cancelButton->hide(); // until we resize + } else { + cancelButton->setParent(this, 0); + } + connect(d->cancel, SIGNAL(clicked()), this, SIGNAL(canceled())); +#ifndef QT_NO_SHORTCUT + d->escapeShortcut = new QShortcut(Qt::Key_Escape, this, SIGNAL(canceled())); +#endif + } else { +#ifndef QT_NO_SHORTCUT + delete d->escapeShortcut; + d->escapeShortcut = 0; +#endif + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + if (cancelButton) +#if !defined(QT_SOFTKEYS_ENABLED) + cancelButton->show(); +#else + { + d->cancelAction = new QAction(cancelButton->text(), cancelButton); + d->cancelAction->setSoftKeyRole(QAction::NegativeSoftKey); + connect(d->cancelAction, SIGNAL(triggered()), this, SIGNAL(canceled())); + addAction(d->cancelAction); + } +#endif +} + +/*! + Sets the cancel button's text to \a cancelButtonText. If the text + is set to QString() then it will cause the cancel button to be + hidden and deleted. + + \sa setCancelButton() +*/ + +void QProgressDialog::setCancelButtonText(const QString &cancelButtonText) +{ + Q_D(QProgressDialog); + d->useDefaultCancelText = false; + + if (!cancelButtonText.isNull()) { + if (d->cancel) { + d->cancel->setText(cancelButtonText); +#ifdef QT_SOFTKEYS_ENABLED + d->cancelAction->setText(cancelButtonText); +#endif + } else { + setCancelButton(new QPushButton(cancelButtonText, this)); + } + } else { + setCancelButton(0); + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); +} + + +/*! + Sets the progress bar widget to \a bar. The progress dialog resizes to + fit. The progress dialog takes ownership of the progress \a bar which + will be deleted when necessary, so do not use a progress bar + allocated on the stack. +*/ + +void QProgressDialog::setBar(QProgressBar *bar) +{ + Q_D(QProgressDialog); + if (!bar) { + qWarning("QProgressDialog::setBar: Cannot set a null progress bar"); + return; + } +#ifndef QT_NO_DEBUG + if (value() > 0) + qWarning("QProgressDialog::setBar: Cannot set a new progress bar " + "while the old one is active"); +#endif + delete d->bar; + d->bar = bar; + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); +} + + +/*! + \property QProgressDialog::wasCanceled + \brief whether the dialog was canceled +*/ + +bool QProgressDialog::wasCanceled() const +{ + Q_D(const QProgressDialog); + return d->cancellation_flag; +} + + +/*! + \property QProgressDialog::maximum + \brief the highest value represented by the progress bar + + The default is 0. + + \sa minimum, setRange() +*/ + +int QProgressDialog::maximum() const +{ + Q_D(const QProgressDialog); + return d->bar->maximum(); +} + +void QProgressDialog::setMaximum(int maximum) +{ + Q_D(QProgressDialog); + d->bar->setMaximum(maximum); +} + +/*! + \property QProgressDialog::minimum + \brief the lowest value represented by the progress bar + + The default is 0. + + \sa maximum, setRange() +*/ + +int QProgressDialog::minimum() const +{ + Q_D(const QProgressDialog); + return d->bar->minimum(); +} + +void QProgressDialog::setMinimum(int minimum) +{ + Q_D(QProgressDialog); + d->bar->setMinimum(minimum); +} + +/*! + Sets the progress dialog's minimum and maximum values + to \a minimum and \a maximum, respectively. + + If \a maximum is smaller than \a minimum, \a minimum becomes the only + legal value. + + If the current value falls outside the new range, the progress + dialog is reset with reset(). + + \sa minimum, maximum +*/ +void QProgressDialog::setRange(int minimum, int maximum) +{ + Q_D(QProgressDialog); + d->bar->setRange(minimum, maximum); +} + + +/*! + Resets the progress dialog. + The progress dialog becomes hidden if autoClose() is true. + + \sa setAutoClose(), setAutoReset() +*/ + +void QProgressDialog::reset() +{ + Q_D(QProgressDialog); +#ifndef QT_NO_CURSOR + if (value() >= 0) { + if (parentWidget()) + parentWidget()->setCursor(d->parentCursor); + } +#endif + if (d->autoClose || d->forceHide) + hide(); + d->bar->reset(); + d->cancellation_flag = false; + d->shown_once = false; + d->forceTimer->stop(); + + /* + I wish we could disconnect the user slot provided to open() here but + unfortunately reset() is usually called before the slot has been invoked. + (reset() is itself invoked when canceled() is emitted.) + */ + if (d->receiverToDisconnectOnClose) + QMetaObject::invokeMethod(this, "_q_disconnectOnClose", Qt::QueuedConnection); +} + +/*! + Resets the progress dialog. wasCanceled() becomes true until + the progress dialog is reset. + The progress dialog becomes hidden. +*/ + +void QProgressDialog::cancel() +{ + Q_D(QProgressDialog); + d->forceHide = true; + reset(); + d->forceHide = false; + d->cancellation_flag = true; +} + + +int QProgressDialog::value() const +{ + Q_D(const QProgressDialog); + return d->bar->value(); +} + +/*! + \property QProgressDialog::value + \brief the current amount of progress made. + + For the progress dialog to work as expected, you should initially set + this property to 0 and finally set it to + QProgressDialog::maximum(); you can call setValue() any number of times + in-between. + + \warning If the progress dialog is modal + (see QProgressDialog::QProgressDialog()), + setValue() calls QApplication::processEvents(), so take care that + this does not cause undesirable re-entrancy in your code. For example, + don't use a QProgressDialog inside a paintEvent()! + + \sa minimum, maximum +*/ +void QProgressDialog::setValue(int progress) +{ + Q_D(QProgressDialog); + if (progress == d->bar->value() + || (d->bar->value() == -1 && progress == d->bar->maximum())) + return; + + d->bar->setValue(progress); + + if (d->shown_once) { + if (isModal()) + QApplication::processEvents(); + } else { + if (progress == 0) { + d->starttime.start(); + d->forceTimer->start(d->showTime); + return; + } else { + bool need_show; + int elapsed = d->starttime.elapsed(); + if (elapsed >= d->showTime) { + need_show = true; + } else { + if (elapsed > minWaitTime) { + int estimate; + int totalSteps = maximum() - minimum(); + int myprogress = progress - minimum(); + if ((totalSteps - myprogress) >= INT_MAX / elapsed) + estimate = (totalSteps - myprogress) / myprogress * elapsed; + else + estimate = elapsed * (totalSteps - myprogress) / myprogress; + need_show = estimate >= d->showTime; + } else { + need_show = false; + } + } + if (need_show) { + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + show(); + d->shown_once = true; + } + } +#ifdef Q_WS_MAC + QApplication::flush(); +#endif + } + + if (progress == d->bar->maximum() && d->autoReset) + reset(); +} + +/*! + Returns a size that fits the contents of the progress dialog. + The progress dialog resizes itself as required, so you should not + need to call this yourself. +*/ + +QSize QProgressDialog::sizeHint() const +{ + Q_D(const QProgressDialog); + QSize sh = d->label ? d->label->sizeHint() : QSize(0, 0); + QSize bh = d->bar->sizeHint(); + int margin = style()->pixelMetric(QStyle::PM_DefaultTopLevelMargin); + int spacing = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); + int h = margin * 2 + bh.height() + sh.height() + spacing; + if (d->cancel) + h += d->cancel->sizeHint().height() + spacing; +#ifdef Q_WS_S60 + if (QApplication::desktop()->size().height() > QApplication::desktop()->size().width()) + return QSize(qMax(QApplication::desktop()->size().width(), sh.width() + 2 * margin), h); + else + return QSize(qMax(QApplication::desktop()->size().height(), sh.width() + 2 * margin), h); +#else + return QSize(qMax(200, sh.width() + 2 * margin), h); +#endif +} + +/*!\reimp +*/ +void QProgressDialog::resizeEvent(QResizeEvent *) +{ + Q_D(QProgressDialog); + d->layout(); +} + +/*! + \reimp +*/ +void QProgressDialog::changeEvent(QEvent *ev) +{ + Q_D(QProgressDialog); + if (ev->type() == QEvent::StyleChange) { + d->layout(); + } else if (ev->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(ev); +} + +/*! + \property QProgressDialog::minimumDuration + \brief the time that must pass before the dialog appears + + If the expected duration of the task is less than the + minimumDuration, the dialog will not appear at all. This prevents + the dialog popping up for tasks that are quickly over. For tasks + that are expected to exceed the minimumDuration, the dialog will + pop up after the minimumDuration time or as soon as any progress + is set. + + If set to 0, the dialog is always shown as soon as any progress is + set. The default is 4000 milliseconds. +*/ +void QProgressDialog::setMinimumDuration(int ms) +{ + Q_D(QProgressDialog); + d->showTime = ms; + if (d->bar->value() == 0) { + d->forceTimer->stop(); + d->forceTimer->start(ms); + } +} + +int QProgressDialog::minimumDuration() const +{ + Q_D(const QProgressDialog); + return d->showTime; +} + + +/*! + \reimp +*/ + +void QProgressDialog::closeEvent(QCloseEvent *e) +{ + emit canceled(); + QDialog::closeEvent(e); +} + +/*! + \property QProgressDialog::autoReset + \brief whether the progress dialog calls reset() as soon as value() equals maximum() + + The default is true. + + \sa setAutoClose() +*/ + +void QProgressDialog::setAutoReset(bool b) +{ + Q_D(QProgressDialog); + d->autoReset = b; +} + +bool QProgressDialog::autoReset() const +{ + Q_D(const QProgressDialog); + return d->autoReset; +} + +/*! + \property QProgressDialog::autoClose + \brief whether the dialog gets hidden by reset() + + The default is true. + + \sa setAutoReset() +*/ + +void QProgressDialog::setAutoClose(bool close) +{ + Q_D(QProgressDialog); + d->autoClose = close; +} + +bool QProgressDialog::autoClose() const +{ + Q_D(const QProgressDialog); + return d->autoClose; +} + +/*! + \reimp +*/ + +void QProgressDialog::showEvent(QShowEvent *e) +{ + Q_D(QProgressDialog); + QDialog::showEvent(e); + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + d->forceTimer->stop(); +} + +/*! + Shows the dialog if it is still hidden after the algorithm has been started + and minimumDuration milliseconds have passed. + + \sa setMinimumDuration() +*/ + +void QProgressDialog::forceShow() +{ + Q_D(QProgressDialog); + d->forceTimer->stop(); + if (d->shown_once || d->cancellation_flag) + return; + + show(); + d->shown_once = true; +} + +/*! + \since 4.5 + \overload + + Opens the dialog and connects its accepted() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QProgressDialog::open(QObject *receiver, const char *member) +{ + Q_D(QProgressDialog); + connect(this, SIGNAL(canceled()), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +QT_END_NAMESPACE + +#include "moc_qprogressdialog.cpp" + +#endif // QT_NO_PROGRESSDIALOG diff --git a/src/widgets/dialogs/qprogressdialog.h b/src/widgets/dialogs/qprogressdialog.h new file mode 100644 index 0000000000..b981e5ef63 --- /dev/null +++ b/src/widgets/dialogs/qprogressdialog.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROGRESSDIALOG_H +#define QPROGRESSDIALOG_H + +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROGRESSDIALOG + +class QPushButton; +class QLabel; +class QProgressBar; +class QTimer; +class QProgressDialogPrivate; + +class Q_WIDGETS_EXPORT QProgressDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QProgressDialog) + Q_PROPERTY(bool wasCanceled READ wasCanceled) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(int value READ value WRITE setValue) + Q_PROPERTY(bool autoReset READ autoReset WRITE setAutoReset) + Q_PROPERTY(bool autoClose READ autoClose WRITE setAutoClose) + Q_PROPERTY(int minimumDuration READ minimumDuration WRITE setMinimumDuration) + Q_PROPERTY(QString labelText READ labelText WRITE setLabelText) + +public: + explicit QProgressDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + QProgressDialog(const QString &labelText, const QString &cancelButtonText, + int minimum, int maximum, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QProgressDialog(); + + void setLabel(QLabel *label); + void setCancelButton(QPushButton *button); + void setBar(QProgressBar *bar); + + bool wasCanceled() const; + + int minimum() const; + int maximum() const; + + int value() const; + + QSize sizeHint() const; + + QString labelText() const; + int minimumDuration() const; + + void setAutoReset(bool reset); + bool autoReset() const; + void setAutoClose(bool close); + bool autoClose() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + +public Q_SLOTS: + void cancel(); + void reset(); + void setMaximum(int maximum); + void setMinimum(int minimum); + void setRange(int minimum, int maximum); + void setValue(int progress); + void setLabelText(const QString &text); + void setCancelButtonText(const QString &text); + void setMinimumDuration(int ms); + +Q_SIGNALS: + void canceled(); + +protected: + void resizeEvent(QResizeEvent *event); + void closeEvent(QCloseEvent *event); + void changeEvent(QEvent *event); + void showEvent(QShowEvent *event); + +protected Q_SLOTS: + void forceShow(); + +private: + Q_DISABLE_COPY(QProgressDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_disconnectOnClose()) +}; + +#endif // QT_NO_PROGRESSDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROGRESSDIALOG_H diff --git a/src/widgets/dialogs/qsidebar.cpp b/src/widgets/dialogs/qsidebar.cpp new file mode 100644 index 0000000000..8efbb8dfcd --- /dev/null +++ b/src/widgets/dialogs/qsidebar.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsidebar_p.h" +#include "qfilesystemmodel.h" + +#ifndef QT_NO_FILEDIALOG + +#include <qaction.h> +#include <qurl.h> +#include <qmenu.h> +#include <qmimedata.h> +#include <qevent.h> +#include <qdebug.h> +#include <qfileiconprovider.h> +#include <qfiledialog.h> + +QT_BEGIN_NAMESPACE + +void QSideBarDelegate::initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const +{ + QStyledItemDelegate::initStyleOption(option,index); + QVariant value = index.data(QUrlModel::EnabledRole); + if (value.isValid()) { + //If the bookmark/entry is not enabled then we paint it in gray + if (!qvariant_cast<bool>(value)) + option->state &= ~QStyle::State_Enabled; + } +} + +/*! + QUrlModel lets you have indexes from a QFileSystemModel to a list. When QFileSystemModel + changes them QUrlModel will automatically update. + + Example usage: File dialog sidebar and combo box + */ +QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(0) +{ +} + +/*! + \reimp +*/ +QStringList QUrlModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + \reimp +*/ +Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags = QStandardItemModel::flags(index); + if (index.isValid()) { + flags &= ~Qt::ItemIsEditable; + // ### some future version could support "moving" urls onto a folder + flags &= ~Qt::ItemIsDropEnabled; + } + + if (index.data(Qt::DecorationRole).isNull()) + flags &= ~Qt::ItemIsEnabled; + + return flags; +} + +/*! + \reimp +*/ +QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const +{ + QList<QUrl> list; + for (int i = 0; i < indexes.count(); ++i) { + if (indexes.at(i).column() == 0) + list.append(indexes.at(i).data(UrlRole).toUrl()); + } + QMimeData *data = new QMimeData(); + data->setUrls(list); + return data; +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + Decide based upon the data if it should be accepted or not + + We only accept dirs and not files +*/ +bool QUrlModel::canDrop(QDragEnterEvent *event) +{ + if (!event->mimeData()->formats().contains(mimeTypes().first())) + return false; + + const QList<QUrl> list = event->mimeData()->urls(); + for (int i = 0; i < list.count(); ++i) { + QModelIndex idx = fileSystemModel->index(list.at(0).toLocalFile()); + if (!fileSystemModel->isDir(idx)) + return false; + } + return true; +} + +/*! + \reimp +*/ +bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (!data->formats().contains(mimeTypes().first())) + return false; + Q_UNUSED(action); + Q_UNUSED(column); + Q_UNUSED(parent); + addUrls(data->urls(), row); + return true; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \reimp + + If the role is the UrlRole then handle otherwise just pass to QStandardItemModel +*/ +bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (value.type() == QVariant::Url) { + QUrl url = value.toUrl(); + QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile()); + //On windows the popup display the "C:\", convert to nativeSeparators + if (showFullPath) + QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString())); + else { + QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole); + QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString()); + } + QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole), + Qt::DecorationRole); + QStandardItemModel::setData(index, url, UrlRole); + return true; + } + return QStandardItemModel::setData(index, value, role); +} + +void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex) +{ + setData(index, url, UrlRole); + if (url.path().isEmpty()) { + setData(index, fileSystemModel->myComputer()); + setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole); + } else { + QString newName; + if (showFullPath) { + //On windows the popup display the "C:\", convert to nativeSeparators + newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString()); + } else { + newName = dirIndex.data().toString(); + } + + QIcon newIcon = qvariant_cast<QIcon>(dirIndex.data(Qt::DecorationRole)); + if (!dirIndex.isValid()) { + newIcon = fileSystemModel->iconProvider()->icon(QFileIconProvider::Folder); + newName = QFileInfo(url.toLocalFile()).fileName(); + if (!invalidUrls.contains(url)) + invalidUrls.append(url); + //The bookmark is invalid then we set to false the EnabledRole + setData(index, false, EnabledRole); + } else { + //The bookmark is valid then we set to true the EnabledRole + setData(index, true, EnabledRole); + } + + // Make sure that we have at least 32x32 images + const QSize size = newIcon.actualSize(QSize(32,32)); + if (size.width() < 32) { + QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32)); + newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation)); + } + + if (index.data().toString() != newName) + setData(index, newName); + QIcon oldIcon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole)); + if (oldIcon.cacheKey() != newIcon.cacheKey()) + setData(index, newIcon, Qt::DecorationRole); + } +} + +void QUrlModel::setUrls(const QList<QUrl> &list) +{ + removeRows(0, rowCount()); + invalidUrls.clear(); + watching.clear(); + addUrls(list, 0); +} + +/*! + Add urls \a list into the list at \a row. If move then movie + existing ones to row. + + \sa dropMimeData() +*/ +void QUrlModel::addUrls(const QList<QUrl> &list, int row, bool move) +{ + if (row == -1) + row = rowCount(); + row = qMin(row, rowCount()); + for (int i = list.count() - 1; i >= 0; --i) { + QUrl url = list.at(i); + if (!url.isValid() || url.scheme() != QLatin1String("file")) + continue; + //this makes sure the url is clean + const QString cleanUrl = QDir::cleanPath(url.toLocalFile()); + url = QUrl::fromLocalFile(cleanUrl); + + for (int j = 0; move && j < rowCount(); ++j) { + QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile(); +#if defined(Q_OS_WIN) + if (index(j, 0).data(UrlRole).toUrl().toLocalFile().toLower() == cleanUrl.toLower()) { +#else + if (index(j, 0).data(UrlRole).toUrl().toLocalFile() == cleanUrl) { +#endif + removeRow(j); + if (j <= row) + row--; + break; + } + } + row = qMax(row, 0); + QModelIndex idx = fileSystemModel->index(cleanUrl); + if (!fileSystemModel->isDir(idx)) + continue; + insertRows(row, 1); + setUrl(index(row, 0), url, idx); + watching.append(qMakePair(idx, cleanUrl)); + } +} + +/*! + Return the complete list of urls in a QList. +*/ +QList<QUrl> QUrlModel::urls() const +{ + QList<QUrl> list; + for (int i = 0; i < rowCount(); ++i) + list.append(data(index(i, 0), UrlRole).toUrl()); + return list; +} + +/*! + QFileSystemModel to get index's from, clears existing rows +*/ +void QUrlModel::setFileSystemModel(QFileSystemModel *model) +{ + if (model == fileSystemModel) + return; + if (fileSystemModel != 0) { + disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + disconnect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(layoutChanged())); + } + fileSystemModel = model; + if (fileSystemModel != 0) { + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(layoutChanged())); + } + clear(); + insertColumns(0, 1); +} + +/* + If one of the index's we are watching has changed update our internal data +*/ +void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + QModelIndex parent = topLeft.parent(); + for (int i = 0; i < watching.count(); ++i) { + QModelIndex index = watching.at(i).first; + if (index.model() && topLeft.model()) { + Q_ASSERT(index.model() == topLeft.model()); + } + if ( index.row() >= topLeft.row() + && index.row() <= bottomRight.row() + && index.column() >= topLeft.column() + && index.column() <= bottomRight.column() + && index.parent() == parent) { + changed(watching.at(i).second); + } + } +} + +/*! + Re-get all of our data, anything could have changed! + */ +void QUrlModel::layoutChanged() +{ + QStringList paths; + for (int i = 0; i < watching.count(); ++i) + paths.append(watching.at(i).second); + watching.clear(); + for (int i = 0; i < paths.count(); ++i) { + QString path = paths.at(i); + QModelIndex newIndex = fileSystemModel->index(path); + watching.append(QPair<QModelIndex, QString>(newIndex, path)); + if (newIndex.isValid()) + changed(path); + } +} + +/*! + The following path changed data update our copy of that data + + \sa layoutChanged() dataChanged() +*/ +void QUrlModel::changed(const QString &path) +{ + for (int i = 0; i < rowCount(); ++i) { + QModelIndex idx = index(i, 0); + if (idx.data(UrlRole).toUrl().toLocalFile() == path) { + setData(idx, idx.data(UrlRole).toUrl()); + } + } +} + +QSidebar::QSidebar(QWidget *parent) : QListView(parent) +{ +} + +void QSidebar::init(QFileSystemModel *model, const QList<QUrl> &newUrls) +{ + // ### TODO make icon size dynamic + setIconSize(QSize(24,24)); + setUniformItemSizes(true); + urlModel = new QUrlModel(this); + urlModel->setFileSystemModel(model); + setModel(urlModel); + setItemDelegate(new QSideBarDelegate(this)); + + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::DragDrop); +#endif + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(showContextMenu(QPoint))); + urlModel->setUrls(newUrls); + setCurrentIndex(this->model()->index(0,0)); +} + +QSidebar::~QSidebar() +{ +} + +#ifndef QT_NO_DRAGANDDROP +void QSidebar::dragEnterEvent(QDragEnterEvent *event) +{ + if (urlModel->canDrop(event)) + QListView::dragEnterEvent(event); +} +#endif // QT_NO_DRAGANDDROP + +QSize QSidebar::sizeHint() const +{ + if (model()) + return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth()); + return QListView::sizeHint(); +} + +void QSidebar::selectUrl(const QUrl &url) +{ + disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); + + selectionModel()->clear(); + for (int i = 0; i < model()->rowCount(); ++i) { + if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) { + selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select); + break; + } + } + + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); +} + +#ifndef QT_NO_MENU +/*! + \internal + + \sa removeEntry() +*/ +void QSidebar::showContextMenu(const QPoint &position) +{ + QList<QAction *> actions; + if (indexAt(position).isValid()) { + QAction *action = new QAction(QFileDialog::tr("Remove"), this); + if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty()) + action->setEnabled(false); + connect(action, SIGNAL(triggered()), this, SLOT(removeEntry())); + actions.append(action); + } + if (actions.count() > 0) + QMenu::exec(actions, mapToGlobal(position)); +} +#endif // QT_NO_MENU + +/*! + \internal + + \sa showContextMenu() +*/ +void QSidebar::removeEntry() +{ + QList<QModelIndex> idxs = selectionModel()->selectedIndexes(); + QList<QPersistentModelIndex> indexes; + for (int i = 0; i < idxs.count(); i++) + indexes.append(idxs.at(i)); + + for (int i = 0; i < indexes.count(); ++i) + if (!indexes.at(i).data(QUrlModel::UrlRole).toUrl().path().isEmpty()) + model()->removeRow(indexes.at(i).row()); +} + +/*! + \internal + + \sa goToUrl() +*/ +void QSidebar::clicked(const QModelIndex &index) +{ + QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl(); + emit goToUrl(url); + selectUrl(url); +} + +/*! + \reimp + Don't automatically select something + */ +void QSidebar::focusInEvent(QFocusEvent *event) +{ + QAbstractScrollArea::focusInEvent(event); + viewport()->update(); +} + +/*! + \reimp + */ +bool QSidebar::event(QEvent * event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent* ke = (QKeyEvent*) event; + if (ke->key() == Qt::Key_Delete) { + removeEntry(); + return true; + } + } + return QListView::event(event); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/widgets/dialogs/qsidebar_p.h b/src/widgets/dialogs/qsidebar_p.h new file mode 100644 index 0000000000..6ad2716780 --- /dev/null +++ b/src/widgets/dialogs/qsidebar_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIDEBAR_H +#define QSIDEBAR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qlistwidget.h> +#include <qstandarditemmodel.h> +#include <qstyleditemdelegate.h> +#include <qurl.h> + +#ifndef QT_NO_FILEDIALOG + +QT_BEGIN_NAMESPACE + +class QFileSystemModel; + +class QSideBarDelegate : public QStyledItemDelegate +{ + public: + QSideBarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {} + void initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const; +}; + +class Q_AUTOTEST_EXPORT QUrlModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Roles { + UrlRole = Qt::UserRole + 1, + EnabledRole = Qt::UserRole + 2 + }; + + QUrlModel(QObject *parent = 0); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; +#ifndef QT_NO_DRAGANDDROP + bool canDrop(QDragEnterEvent *event); + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); +#endif + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole); + + void setUrls(const QList<QUrl> &list); + void addUrls(const QList<QUrl> &urls, int row = -1, bool move = true); + QList<QUrl> urls() const; + void setFileSystemModel(QFileSystemModel *model); + bool showFullPath; + +private Q_SLOTS: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void layoutChanged(); + +private: + void setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex); + void changed(const QString &path); + void addIndexToWatch(const QString &path, const QModelIndex &index); + QFileSystemModel *fileSystemModel; + QList<QPair<QModelIndex, QString> > watching; + QList<QUrl> invalidUrls; +}; + +class Q_AUTOTEST_EXPORT QSidebar : public QListView +{ + Q_OBJECT + +Q_SIGNALS: + void goToUrl(const QUrl &url); + +public: + QSidebar(QWidget *parent = 0); + void init(QFileSystemModel *model, const QList<QUrl> &newUrls); + ~QSidebar(); + + QSize sizeHint() const; + + void setUrls(const QList<QUrl> &list) { urlModel->setUrls(list); } + void addUrls(const QList<QUrl> &list, int row) { urlModel->addUrls(list, row); } + QList<QUrl> urls() const { return urlModel->urls(); } + + void selectUrl(const QUrl &url); + +protected: + bool event(QEvent * e); + void focusInEvent(QFocusEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event); +#endif + +private Q_SLOTS: + void clicked(const QModelIndex &index); +#ifndef QT_NO_MENU + void showContextMenu(const QPoint &position); +#endif + void removeEntry(); + +private: + QUrlModel *urlModel; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + +#endif // QSIDEBAR_H + diff --git a/src/widgets/dialogs/qwizard.cpp b/src/widgets/dialogs/qwizard.cpp new file mode 100644 index 0000000000..83bdaa0e55 --- /dev/null +++ b/src/widgets/dialogs/qwizard.cpp @@ -0,0 +1,3928 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $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 "qpushbutton.h" +#include "qset.h" +#include "qstyle.h" +#include "qvarlengtharray.h" +#if defined(Q_WS_MAC) +#include "private/qt_mac_p.h" +#include "qlibrary.h" +#elif !defined(QT_NO_STYLE_WINDOWSVISTA) +#include "qwizard_win_p.h" +#include "qtimer.h" +#endif + +#include "private/qdialog_p.h" +#include <qdebug.h> + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp +#endif + +#include <string.h> // for memset() + +#ifdef QT_SOFTKEYS_ENABLED +#include "qaction.h" +#endif + +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<QWizardDefaultProperty> &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<QWizardDefaultProperty> &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 <QWizard *>(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<QWizardField> pendingFields; + mutable TriState completeState; + bool explicitlyFinal; + bool commit; + QMap<int, QString> 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 +{ + QWizard *wizard; + QWizardPrivate *wizardPrivate; +public: + QWizardAntiFlickerWidget(QWizard *wizard, QWizardPrivate *wizardPrivate) + : QWidget(wizard) + , wizard(wizard) + , wizardPrivate(wizardPrivate) {} +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +protected: + void paintEvent(QPaintEvent *); +#endif +}; + +class QWizardPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QWizard) + +public: + typedef QMap<int, QWizardPage *> 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; +#ifdef QT_SOFTKEYS_ENABLED + softKeys[i] = 0; +#endif + } +#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; + void 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_WS_MAC + static QPixmap findDefaultBackgroundPixmap(); +#endif + + PageMap pageMap; + QVector<QWizardField> fields; + QMap<QString, int> fieldIndexMap; + QVector<QWizardDefaultProperty> defaultPropertyTable; + QList<int> history; + QSet<int> 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<int, QString> buttonCustomTexts; + bool buttonsHaveCustomLayout; + QList<QWizard::WizardButton> 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; +#ifdef QT_SOFTKEYS_ENABLED + mutable QAction *softKeys[QWizard::NButtons]; +#endif + + 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<int> original = initialized; + QSet<int>::const_iterator i = original.constBegin(); + QSet<int>::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(); +} + +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); + // Make navigation buttons detectable as passive interactor in designer + switch (which) { + case QWizard::CommitButton: + case QWizard::FinishButton: + case QWizard::CancelButton: + break; + default: { + QString objectName = QLatin1String("__qt__passive_wizardbutton"); + objectName += QString::number(which); + pushButton->setObjectName(objectName); + } + break; + } +#ifdef Q_WS_MAC + pushButton->setAutoDefault(false); +#endif + pushButton->hide(); +#ifdef Q_CC_HPACC + const_cast<QWizardPrivate *>(this)->btns[which] = pushButton; +#else + btns[which] = pushButton; +#endif + if (which < QWizard::NStandardButtons) + pushButton->setText(buttonDefaultText(wizStyle, which, this)); + +#ifdef QT_SOFTKEYS_ENABLED + QAction *softKey = new QAction(pushButton->text(), pushButton); + QAction::SoftKeyRole softKeyRole; + switch(which) { + case QWizard::NextButton: + case QWizard::FinishButton: + case QWizard::CancelButton: + softKeyRole = QAction::NegativeSoftKey; + break; + case QWizard::BackButton: + case QWizard::CommitButton: + case QWizard::HelpButton: + case QWizard::CustomButton1: + case QWizard::CustomButton2: + case QWizard::CustomButton3: + default: + softKeyRole = QAction::PositiveSoftKey; + break; + } + softKey->setSoftKeyRole(softKeyRole); + softKeys[which] = softKey; +#endif + 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())); + } + +#ifdef QT_SOFTKEYS_ENABLED + QObject::connect(softKeys[which], SIGNAL(triggered()), btns[which], SIGNAL(clicked())); +#endif +} + +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)); +#ifdef QT_SOFTKEYS_ENABLED + softKeys[i]->setText(btns[i]->text()); +#endif + } + } +} + +void QWizardPrivate::updateButtonLayout() +{ + if (buttonsHaveCustomLayout) { + QVarLengthArray<QWizard::WizardButton> 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(); +} + +void QWizardPrivate::handleAeroStyleChange() +{ + Q_Q(QWizard); + + if (inHandleAeroStyleChange) + return; // prevent recursion + inHandleAeroStyleChange = true; + + vistaHelper->disconnectBackButton(); + q->removeEventFilter(vistaHelper); + + if (isVistaThemeEnabled()) { + if (isVistaThemeEnabled(QVistaHelper::VistaAero)) { + 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)); + } else { + vistaHelper->setDWMTitleBar(QVistaHelper::NormalTitleBar); + q->setMouseTracking(true); + antiFlickerWidget->move(0, vistaHelper->topOffset()); + vistaHelper->backButton()->move(0, -1); // ### should ideally work with (0, 0) + } + 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(); + vistaHelper->setTitleBarIconAndCaptionVisible(true); + } + + _q_updateButtonStates(); + + if (q->isVisible()) + vistaHelper->setWindowPosHack(); + + inHandleAeroStyleChange = false; +} +#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<QPushButton *>(btn.next)) + nextPush->setDefault(canContinue && useDefault && !commitPage); + if (QPushButton *commitPush = qobject_cast<QPushButton *>(btn.commit)) + commitPush->setDefault(canContinue && useDefault && commitPage); + if (QPushButton *finishPush = qobject_cast<QPushButton *>(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 + +#ifdef QT_SOFTKEYS_ENABLED + QAbstractButton *wizardButton; + for (int i = 0; i < QWizard::NButtons; ++i) { + wizardButton = btns[i]; + if (wizardButton && !wizardButton->testAttribute(Qt::WA_WState_Hidden)) { + wizardButton->hide(); + q->addAction(softKeys[i]); + } else { + q->removeAction(softKeys[i]); + } + } +#endif + + enableUpdates(); +} + +void QWizardPrivate::_q_handleFieldObjectDestroyed(QObject *object) +{ + QVector<QWizardField>::iterator it = fields.begin(); + while (it != fields.end()) { + const QWizardField &field = *it; + if (field.object == object) { + fieldIndexMap.remove(field.name); + it = fields.erase(it); + } else { + ++it; + } + } +} + +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_WS_MAC + +QPixmap QWizardPrivate::findDefaultBackgroundPixmap() +{ + QCFType<CFURLRef> url; + const int ExpectedImageWidth = 242; + const int ExpectedImageHeight = 414; + if (LSFindApplicationForInfo(kLSUnknownCreator, CFSTR("com.apple.KeyboardSetupAssistant"), + 0, 0, &url) == noErr) { + QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url); + if (bundle) { + url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("tif"), 0); + if (url) { + QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, 0); + QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + if (image) { + int width = CGImageGetWidth(image); + int height = CGImageGetHeight(image); + if (width == ExpectedImageWidth && height == ExpectedImageHeight) + return QPixmap::fromMacCGImageRef(image); + } + } + } + } + return QPixmap(); + +} + +#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. + + 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 examples/dialogs/trivialwizard/trivialwizard.cpp 1 + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 3 + \dots + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 4 + \codeline + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 5 + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 7 + \dots + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 8 + \codeline + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 10 + + \section1 Wizard Look and Feel + + QWizard supports four wizard looks: + + \list + \o ClassicStyle + \o ModernStyle + \o MacStyle + \o 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 \o ClassicStyle + \o ModernStyle + \o MacStyle + \o AeroStyle + \row \o \inlineimage qtwizard-classic1.png + \o \inlineimage qtwizard-modern1.png + \o \inlineimage qtwizard-mac1.png + \o \inlineimage qtwizard-aero1.png + \row \o \inlineimage qtwizard-classic2.png + \o \inlineimage qtwizard-modern2.png + \o \inlineimage qtwizard-mac2.png + \o \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 \gui 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 \gui 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 examples/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 + \o A \l{QWizardPage::}{title}. + \o A \l{QWizardPage::}{subTitle}. + \o A set of pixmaps, which may or may not be honored, depending + on the wizard's style: + \list + \o WatermarkPixmap (used by ClassicStyle and ModernStyle) + \o BannerPixmap (used by ModernStyle) + \o LogoPixmap (used by ClassicStyle and ModernStyle) + \o 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 examples/dialogs/classwizard/classwizard.cpp 8 + \dots + \snippet examples/dialogs/classwizard/classwizard.cpp 10 + \snippet examples/dialogs/classwizard/classwizard.cpp 11 + \dots + \snippet examples/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 examples/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 OuputFilePage. 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 \gui Next and/or \gui 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 \gui Next and/or \gui 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 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 examples/dialogs/classwizard/classwizard.cpp 0 + \dots + \snippet examples/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 \gui 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 \gui 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 examples/dialogs/licensewizard/licensewizard.h 0 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.h 2 + \dots + \snippet examples/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 examples/dialogs/licensewizard/licensewizard.cpp 1 + \dots + \snippet examples/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 examples/dialogs/licensewizard/licensewizard.cpp 18 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 23 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 24 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 25 + \codeline + \snippet examples/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 doc/src/snippets/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 examples/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 \gui Back button (\gui {Go Back} on Mac OS X) + \value NextButton The \gui Next button (\gui Continue on Mac OS X) + \value CommitButton The \gui Commit button + \value FinishButton The \gui Finish button (\gui Done on Mac OS X) + \value CancelButton The \gui Cancel button (see also NoCancelButton) + \value HelpButton The \gui 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 \gui Next or \gui Finish button the + dialog's \l{QPushButton::setDefault()}{default button}. + \value NoBackButtonOnStartPage Don't show the \gui Back button on the start page. + \value NoBackButtonOnLastPage Don't show the \gui Back button on the last page. + \value DisabledBackButtonOnLastPage Disable the \gui Back button on the last page. + \value HaveNextButtonOnLastPage Show the (disabled) \gui Next button on the last page. + \value HaveFinishButtonOnEarlyPages Show the (disabled) \gui Finish button on non-final pages. + \value NoCancelButton Don't show the \gui Cancel button. + \value CancelButtonOnLeft Put the \gui Cancel button on the left of \gui Back (rather than on + the right of \gui Finish or \gui Next). + \value HaveHelpButton Show the \gui Help button. + \value HelpButtonOnRight Put the \gui 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_WS_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<QWizardField> &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 true if the page history contains page \a id; otherwise, + returns false. + + Pressing \gui 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 \gui Back marks the current page as "unvisited" again. + + \sa hasVisitedPage() +*/ +QList<int> QWizard::visitedPages() const +{ + Q_D(const QWizard); + return d->history; +} + +/*! + Returns the list of page IDs. + \since 4.5 +*/ +QList<int> 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 currentIdChanged(), 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) + if (aeroStyleChange) + d->handleAeroStyleChange(); +#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 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 + \o Windows: HelpButtonOnRight. + \o Mac OS X: NoDefaultButton and NoCancelButton. + \o 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 \gui Next button is called \gui + Continue. + + To add extra buttons to the wizard (e.g., a \gui 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 \gui Next button is called \gui + 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 doc/src/snippets/code/src_gui_dialogs_qwizard.cpp 1 + + \sa setButton(), setButtonText(), setOptions() +*/ +void QWizard::setButtonLayout(const QList<WizardButton> &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 \gui 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_WS_MAC + 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 \o Widget \o Property \o Change Notification Signal + \row \o QAbstractButton \o bool \l{QAbstractButton::}{checked} \o \l{QAbstractButton::}{toggled()} + \row \o QAbstractSlider \o int \l{QAbstractSlider::}{value} \o \l{QAbstractSlider::}{valueChanged()} + \row \o QComboBox \o int \l{QComboBox::}{currentIndex} \o \l{QComboBox::}{currentIndexChanged()} + \row \o QDateTimeEdit \o QDateTime \l{QDateTimeEdit::}{dateTime} \o \l{QDateTimeEdit::}{dateTimeChanged()} + \row \o QLineEdit \o QString \l{QLineEdit::}{text} \o \l{QLineEdit::}{textChanged()} + \row \o QListWidget \o int \l{QListWidget::}{currentRow} \o \l{QListWidget::}{currentRowChanged()} + \row \o QSpinBox \o int \l{QSpinBox::}{value} \o \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(); +#ifdef Q_WS_S60 + QSize extra(QApplication::desktop()->availableGeometry(QCursor::pos()).size()); +#else + QSize extra(500, 360); +#endif + 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 \gui Help button. + + By default, no \gui Help button is shown. Call + setOption(HaveHelpButton, true) to have one. + + Example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 0 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 5 + \snippet examples/dialogs/licensewizard/licensewizard.cpp 7 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 8 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 10 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 12 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 14 + \codeline + \snippet examples/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 \gui 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 \gui Next or \gui 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; + d->wizStyle = AeroStyle; + d->handleAeroStyleChange(); + } + else if (d->isVistaThemeEnabled()) { + 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_WS_WIN) +/*! + \reimp +*/ +bool QWizard::winEvent(MSG *message, long *result) +{ +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + Q_D(QWizard); + if (d->isVistaThemeEnabled()) { + const bool winEventResult = d->vistaHelper->handleWinEvent(message, result); + if (QVistaHelper::vistaState() != d->vistaState) { + d->vistaState = QVistaHelper::vistaState(); + d->vistaStateChanged = true; + setWizardStyle(AeroStyle); + } + return winEventResult; + } else { + return QDialog::winEvent(message, result); + } +#else + return QDialog::winEvent(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 \gui 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 \gui 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 + \gui Next or \gui Finish to perform some last-minute validation. + If it returns 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 \gui + Next or \gui 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 \gui 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. + + 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 + \o initializePage() is called to initialize the page's contents + when the user clicks the wizard's \gui 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. + \o cleanupPage() is called to reset the page's contents when the + user clicks the wizard's \gui Back button. + \o validatePage() validates the page when the user clicks \gui + Next or \gui Finish. It is often used to show an error message + if the user has entered incomplete or invalid information. + \o 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. + \o isComplete() is called to determine whether the \gui Next + and/or \gui 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 \gui Next button and the \gui Finish button of a + wizard are mutually exclusive. If isFinalPage() returns true, \gui + Finish is available; otherwise, \gui Next is available. By + default, isFinalPage() is true only when nextId() returns -1. If + you want to show \gui Next and \gui 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())); +} + +/*! + \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 \gui 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 examples/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 \gui 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<QWizardField> &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 \gui Next or \gui Finish to perform some + last-minute validation. If it returns true, the next page is shown + (or the wizard finishes); otherwise, the current page stays up. + + The default implementation returns true. + + When possible, it is usually better style to disable the \gui + Next or \gui 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 \gui Next or \gui Finish button should be enabled or + disabled. + + The default implementation returns true if all \l{mandatory + fields} are filled; otherwise, it returns 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://qt.nokia.com/doc/qq/qq22-qwizard.html#validatebeforeitstoolate} + {here}. + + \sa completeChanged(), isFinalPage() +*/ +bool QWizardPage::isComplete() const +{ + Q_D(const QWizardPage); + + if (!d->wizard) + return true; + + const QVector<QWizardField> &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<QLineEdit *>(field.object)) { + if (!lineEdit->hasAcceptableInput()) + return false; + } +#endif +#ifndef QT_NO_SPINBOX + if (QAbstractSpinBox *spinBox = qobject_cast<QAbstractSpinBox *>(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 true and the \gui + Finish button is visible (and enabled if isComplete() returns + true). + + After calling setFinalPage(false), isFinalPage() returns true if + nextId() returns -1; otherwise, it returns 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 \gui + Finish button should be shown for this page or not. + + By default, it returns true if there is no next page + (i.e., nextId() returns -1); otherwise, it returns 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 \gui Back or \gui Cancel. + + A \gui Commit button replaces the \gui Next button on a commit page. Clicking this + button simply calls QWizard::next() just like clicking \gui Next does. + + A page entered directly from a commit page has its \gui 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 true if this page is a commit page; otherwise returns 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 \gui Next button is called \gui + 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 \gui 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 examples/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 examples/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 \gui Next and/or + \gui 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 \o Widget \o Property \o Change Notification Signal + \row \o QAbstractButton \o bool \l{QAbstractButton::}{checked} \o \l{QAbstractButton::}{toggled()} + \row \o QAbstractSlider \o int \l{QAbstractSlider::}{value} \o \l{QAbstractSlider::}{valueChanged()} + \row \o QComboBox \o int \l{QComboBox::}{currentIndex} \o \l{QComboBox::}{currentIndexChanged()} + \row \o QDateTimeEdit \o QDateTime \l{QDateTimeEdit::}{dateTime} \o \l{QDateTimeEdit::}{dateTimeChanged()} + \row \o QLineEdit \o QString \l{QLineEdit::}{text} \o \l{QLineEdit::}{textChanged()} + \row \o QListWidget \o int \l{QListWidget::}{currentRow} \o \l{QListWidget::}{currentRowChanged()} + \row \o QSpinBox \o int \l{QSpinBox::}{value} \o \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 diff --git a/src/widgets/dialogs/qwizard.h b/src/widgets/dialogs/qwizard.h new file mode 100644 index 0000000000..77eef53037 --- /dev/null +++ b/src/widgets/dialogs/qwizard.h @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIZARD_H +#define QWIZARD_H + +#include <QtWidgets/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_WIZARD + +class QAbstractButton; +class QWizardPage; +class QWizardPrivate; + +class Q_WIDGETS_EXPORT QWizard : public QDialog +{ + Q_OBJECT + Q_ENUMS(WizardStyle WizardOption) + Q_FLAGS(WizardOptions) + Q_PROPERTY(WizardStyle wizardStyle READ wizardStyle WRITE setWizardStyle) + Q_PROPERTY(WizardOptions options READ options WRITE setOptions) + Q_PROPERTY(Qt::TextFormat titleFormat READ titleFormat WRITE setTitleFormat) + Q_PROPERTY(Qt::TextFormat subTitleFormat READ subTitleFormat WRITE setSubTitleFormat) + Q_PROPERTY(int startId READ startId WRITE setStartId) + Q_PROPERTY(int currentId READ currentId NOTIFY currentIdChanged) + +public: + enum WizardButton { + BackButton, + NextButton, + CommitButton, + FinishButton, + CancelButton, + HelpButton, + CustomButton1, + CustomButton2, + CustomButton3, + Stretch, + + NoButton = -1, + NStandardButtons = 6, + NButtons = 9 + }; + + enum WizardPixmap { + WatermarkPixmap, + LogoPixmap, + BannerPixmap, + BackgroundPixmap, + NPixmaps + }; + + enum WizardStyle { + ClassicStyle, + ModernStyle, + MacStyle, + AeroStyle, + NStyles + }; + + enum WizardOption { + IndependentPages = 0x00000001, + IgnoreSubTitles = 0x00000002, + ExtendedWatermarkPixmap = 0x00000004, + NoDefaultButton = 0x00000008, + NoBackButtonOnStartPage = 0x00000010, + NoBackButtonOnLastPage = 0x00000020, + DisabledBackButtonOnLastPage = 0x00000040, + HaveNextButtonOnLastPage = 0x00000080, + HaveFinishButtonOnEarlyPages = 0x00000100, + NoCancelButton = 0x00000200, + CancelButtonOnLeft = 0x00000400, + HaveHelpButton = 0x00000800, + HelpButtonOnRight = 0x00001000, + HaveCustomButton1 = 0x00002000, + HaveCustomButton2 = 0x00004000, + HaveCustomButton3 = 0x00008000 + }; + + Q_DECLARE_FLAGS(WizardOptions, WizardOption) + + explicit QWizard(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QWizard(); + + int addPage(QWizardPage *page); + void setPage(int id, QWizardPage *page); + void removePage(int id); + QWizardPage *page(int id) const; + bool hasVisitedPage(int id) const; + QList<int> visitedPages() const; // ### visitedIds()? + QList<int> pageIds() const; + void setStartId(int id); + int startId() const; + QWizardPage *currentPage() const; + int currentId() const; + + virtual bool validateCurrentPage(); + virtual int nextId() const; + + void setField(const QString &name, const QVariant &value); + QVariant field(const QString &name) const; + + void setWizardStyle(WizardStyle style); + WizardStyle wizardStyle() const; + + void setOption(WizardOption option, bool on = true); + bool testOption(WizardOption option) const; + void setOptions(WizardOptions options); + WizardOptions options() const; + + void setButtonText(WizardButton which, const QString &text); + QString buttonText(WizardButton which) const; + void setButtonLayout(const QList<WizardButton> &layout); + void setButton(WizardButton which, QAbstractButton *button); + QAbstractButton *button(WizardButton which) const; + + void setTitleFormat(Qt::TextFormat format); + Qt::TextFormat titleFormat() const; + void setSubTitleFormat(Qt::TextFormat format); + Qt::TextFormat subTitleFormat() const; + void setPixmap(WizardPixmap which, const QPixmap &pixmap); + QPixmap pixmap(WizardPixmap which) const; + + void setSideWidget(QWidget *widget); + QWidget *sideWidget() const; + + void setDefaultProperty(const char *className, const char *property, + const char *changedSignal); + + void setVisible(bool visible); + QSize sizeHint() const; + +Q_SIGNALS: + void currentIdChanged(int id); + void helpRequested(); + void customButtonClicked(int which); + void pageAdded(int id); + void pageRemoved(int id); + +public Q_SLOTS: + void back(); + void next(); + void restart(); + +protected: + bool event(QEvent *event); + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); +#if defined(Q_WS_WIN) + bool winEvent(MSG * message, long * result); +#endif + void done(int result); + virtual void initializePage(int id); + virtual void cleanupPage(int id); + +private: + Q_DISABLE_COPY(QWizard) + Q_DECLARE_PRIVATE(QWizard) + Q_PRIVATE_SLOT(d_func(), void _q_emitCustomButtonClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_updateButtonStates()) + Q_PRIVATE_SLOT(d_func(), void _q_handleFieldObjectDestroyed(QObject *)) + + friend class QWizardPage; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWizard::WizardOptions) + +class QWizardPagePrivate; + +class Q_WIDGETS_EXPORT QWizardPage : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString subTitle READ subTitle WRITE setSubTitle) + +public: + QWizardPage(QWidget *parent = 0); + + void setTitle(const QString &title); + QString title() const; + void setSubTitle(const QString &subTitle); + QString subTitle() const; + void setPixmap(QWizard::WizardPixmap which, const QPixmap &pixmap); + QPixmap pixmap(QWizard::WizardPixmap which) const; + void setFinalPage(bool finalPage); + bool isFinalPage() const; + void setCommitPage(bool commitPage); + bool isCommitPage() const; + void setButtonText(QWizard::WizardButton which, const QString &text); + QString buttonText(QWizard::WizardButton which) const; + + virtual void initializePage(); + virtual void cleanupPage(); + virtual bool validatePage(); + virtual bool isComplete() const; + virtual int nextId() const; + +Q_SIGNALS: + void completeChanged(); + +protected: + void setField(const QString &name, const QVariant &value); + QVariant field(const QString &name) const; + void registerField(const QString &name, QWidget *widget, const char *property = 0, + const char *changedSignal = 0); + QWizard *wizard() const; + +private: + Q_DISABLE_COPY(QWizardPage) + Q_DECLARE_PRIVATE(QWizardPage) + Q_PRIVATE_SLOT(d_func(), void _q_maybeEmitCompleteChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_updateCachedCompleteState()) + + friend class QWizard; + friend class QWizardPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_WIZARD + +#endif // QWIZARD_H diff --git a/src/widgets/dialogs/qwizard_win.cpp b/src/widgets/dialogs/qwizard_win.cpp new file mode 100644 index 0000000000..6177700d8c --- /dev/null +++ b/src/widgets/dialogs/qwizard_win.cpp @@ -0,0 +1,773 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_WIZARD +#ifndef QT_NO_STYLE_WINDOWSVISTA + +#include "qwizard_win_p.h" +#include <private/qsystemlibrary_p.h> +#include <private/qapplication_p.h> +#include "qplatformnativeinterface_qpa.h" +#include "qwizard.h" +#include "qpaintengine.h" +#include "qapplication.h" +#include <QtGui/QMouseEvent> +#include <QtWidgets/QDesktopWidget> + +// Note, these tests are duplicates in qwindowsxpstyle_p.h. +#ifdef Q_CC_GNU +# include <w32api.h> +# if (__W32API_MAJOR_VERSION >= 3 || (__W32API_MAJOR_VERSION == 2 && __W32API_MINOR_VERSION >= 5)) +# ifdef _WIN32_WINNT +# undef _WIN32_WINNT +# endif +# define _WIN32_WINNT 0x0501 +# include <commctrl.h> +# endif +#endif + +#include <uxtheme.h> + +QT_BEGIN_NAMESPACE + +//DWM related +typedef struct { //MARGINS + int cxLeftWidth; // width of left border that retains its size + int cxRightWidth; // width of right border that retains its size + int cyTopHeight; // height of top border that retains its size + int cyBottomHeight; // height of bottom border that retains its size +} WIZ_MARGINS; +typedef struct { //DTTOPTS + DWORD dwSize; + DWORD dwFlags; + COLORREF crText; + COLORREF crBorder; + COLORREF crShadow; + int eTextShadowType; + POINT ptShadowOffset; + int iBorderSize; + int iFontPropId; + int iColorPropId; + int iStateId; + BOOL fApplyOverlay; + int iGlowSize; +} WIZ_DTTOPTS; + +typedef struct { + DWORD dwFlags; + DWORD dwMask; +} WIZ_WTA_OPTIONS; + +#define WIZ_WM_THEMECHANGED 0x031A +#define WIZ_WM_DWMCOMPOSITIONCHANGED 0x031E + +enum WIZ_WINDOWTHEMEATTRIBUTETYPE { + WIZ_WTA_NONCLIENT = 1 +}; + +#define WIZ_WTNCA_NODRAWCAPTION 0x00000001 +#define WIZ_WTNCA_NODRAWICON 0x00000002 + +#define WIZ_DT_CENTER 0x00000001 //DT_CENTER +#define WIZ_DT_VCENTER 0x00000004 +#define WIZ_DT_SINGLELINE 0x00000020 +#define WIZ_DT_NOPREFIX 0x00000800 + +enum WIZ_NAVIGATIONPARTS { //NAVIGATIONPARTS + WIZ_NAV_BACKBUTTON = 1, + WIZ_NAV_FORWARDBUTTON = 2, + WIZ_NAV_MENUBUTTON = 3, +}; + +enum WIZ_NAV_BACKBUTTONSTATES { //NAV_BACKBUTTONSTATES + WIZ_NAV_BB_NORMAL = 1, + WIZ_NAV_BB_HOT = 2, + WIZ_NAV_BB_PRESSED = 3, + WIZ_NAV_BB_DISABLED = 4, +}; + +#define WIZ_TMT_CAPTIONFONT (801) //TMT_CAPTIONFONT +#define WIZ_DTT_COMPOSITED (1UL << 13) //DTT_COMPOSITED +#define WIZ_DTT_GLOWSIZE (1UL << 11) //DTT_GLOWSIZE + +#define WIZ_WM_NCMOUSELEAVE 674 //WM_NCMOUSELEAVE + +#define WIZ_WP_CAPTION 1 //WP_CAPTION +#define WIZ_CS_ACTIVE 1 //CS_ACTIVE +#define WIZ_TMT_FILLCOLORHINT 3821 //TMT_FILLCOLORHINT +#define WIZ_TMT_BORDERCOLORHINT 3822 //TMT_BORDERCOLORHINT + +typedef BOOL (WINAPI *PtrDwmDefWindowProc)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult); +typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled); +typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const WIZ_MARGINS* pMarInset); +typedef HRESULT (WINAPI *PtrSetWindowThemeAttribute)(HWND hwnd, enum WIZ_WINDOWTHEMEATTRIBUTETYPE eAttribute, PVOID pvAttribute, DWORD cbAttribute); + +static PtrDwmDefWindowProc pDwmDefWindowProc = 0; +static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled = 0; +static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0; +static PtrSetWindowThemeAttribute pSetWindowThemeAttribute = 0; + +//Theme related +typedef bool (WINAPI *PtrIsAppThemed)(); +typedef bool (WINAPI *PtrIsThemeActive)(); +typedef HANDLE (WINAPI *PtrOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI *PtrCloseThemeData)(HANDLE hTheme); +typedef HRESULT (WINAPI *PtrGetThemeSysFont)(HANDLE hTheme, int iFontId, LOGFONTW *plf); +typedef HRESULT (WINAPI *PtrDrawThemeTextEx)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int cchText, DWORD dwTextFlags, LPRECT pRect, const WIZ_DTTOPTS *pOptions); +typedef HRESULT (WINAPI *PtrDrawThemeBackground)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect); +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HRESULT (WINAPI *PtrGetThemeColor)(HANDLE hTheme, int iPartId, int iStateId, int iPropId, OUT COLORREF *pColor); + +static PtrIsAppThemed pIsAppThemed = 0; +static PtrIsThemeActive pIsThemeActive = 0; +static PtrOpenThemeData pOpenThemeData = 0; +static PtrCloseThemeData pCloseThemeData = 0; +static PtrGetThemeSysFont pGetThemeSysFont = 0; +static PtrDrawThemeTextEx pDrawThemeTextEx = 0; +static PtrDrawThemeBackground pDrawThemeBackground = 0; +static PtrGetThemePartSize pGetThemePartSize = 0; +static PtrGetThemeColor pGetThemeColor = 0; + +bool QVistaHelper::is_vista = false; +QVistaHelper::VistaState QVistaHelper::cachedVistaState = QVistaHelper::Dirty; + +/****************************************************************************** +** QVistaBackButton +*/ + +QVistaBackButton::QVistaBackButton(QWidget *widget) + : QAbstractButton(widget) +{ + setFocusPolicy(Qt::NoFocus); +} + +QSize QVistaBackButton::sizeHint() const +{ + ensurePolished(); + int size = int(QStyleHelper::dpiScaled(32)); + int width = size, height = size; +/* + HANDLE theme = pOpenThemeData(0, L"Navigation"); + SIZE size; + if (pGetThemePartSize(theme, 0, WIZ_NAV_BACKBUTTON, WIZ_NAV_BB_NORMAL, 0, TS_TRUE, &size) == S_OK) { + width = size.cx; + height = size.cy; + } +*/ + return QSize(width, height); +} + +void QVistaBackButton::enterEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::enterEvent(event); +} + +void QVistaBackButton::leaveEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::leaveEvent(event); +} + +void QVistaBackButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QRect r = rect(); + HANDLE theme = pOpenThemeData(0, L"Navigation"); + //RECT rect; + RECT clipRect; + int xoffset = QWidget::mapToParent(r.topLeft()).x() - 1; + int yoffset = QWidget::mapToParent(r.topLeft()).y() - 1; + + clipRect.top = r.top() + yoffset; + clipRect.bottom = r.bottom() + yoffset; + clipRect.left = r.left() + xoffset; + clipRect.right = r.right() + xoffset; + + int state = WIZ_NAV_BB_NORMAL; + if (!isEnabled()) + state = WIZ_NAV_BB_DISABLED; + else if (isDown()) + state = WIZ_NAV_BB_PRESSED; + else if (underMouse()) + state = WIZ_NAV_BB_HOT; + + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + HDC hdc = static_cast<HDC>(nativeInterface->nativeResourceForBackingStore("getDC", backingStore())); + pDrawThemeBackground(theme, hdc, WIZ_NAV_BACKBUTTON, state, &clipRect, &clipRect); +} + +/****************************************************************************** +** QVistaHelper +*/ + +QVistaHelper::QVistaHelper(QWizard *wizard) + : QObject(wizard) + , pressed(false) + , wizard(wizard) + , backButton_(0) +{ + is_vista = resolveSymbols(); + if (is_vista) + backButton_ = new QVistaBackButton(wizard); + + // Handle diff between Windows 7 and Vista + iconSpacing = QStyleHelper::dpiScaled(7); + textSpacing = QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? + iconSpacing : QStyleHelper::dpiScaled(20); +} + +QVistaHelper::~QVistaHelper() +{ +} + +bool QVistaHelper::isCompositionEnabled() +{ + bool value = is_vista; + if (is_vista) { + HRESULT hr; + BOOL bEnabled; + + hr = pDwmIsCompositionEnabled(&bEnabled); + value = (SUCCEEDED(hr) && bEnabled); + } + return value; +} + +bool QVistaHelper::isThemeActive() +{ + return is_vista && pIsThemeActive(); +} + +QVistaHelper::VistaState QVistaHelper::vistaState() +{ + if (cachedVistaState == Dirty) + cachedVistaState = + isCompositionEnabled() ? VistaAero : isThemeActive() ? VistaBasic : Classic; + return cachedVistaState; +} + +QColor QVistaHelper::basicWindowFrameColor() +{ + DWORD rgb; + HWND handle = QApplicationPrivate::getHWNDForWidget(QApplication::desktop()); + HANDLE hTheme = pOpenThemeData(handle, L"WINDOW"); + pGetThemeColor( + hTheme, WIZ_WP_CAPTION, WIZ_CS_ACTIVE, + wizard->isActiveWindow() ? WIZ_TMT_FILLCOLORHINT : WIZ_TMT_BORDERCOLORHINT, + &rgb); + BYTE r = GetRValue(rgb); + BYTE g = GetGValue(rgb); + BYTE b = GetBValue(rgb); + return QColor(r, g, b); +} + +bool QVistaHelper::setDWMTitleBar(TitleBarChangeType type) +{ + bool value = false; + if (vistaState() == VistaAero) { + WIZ_MARGINS mar = {0}; + if (type == NormalTitleBar) + mar.cyTopHeight = 0; + else + mar.cyTopHeight = titleBarSize() + topOffset(); + HWND wizardHandle = QApplicationPrivate::getHWNDForWidget(wizard); + HRESULT hr = pDwmExtendFrameIntoClientArea(wizardHandle, &mar); + value = SUCCEEDED(hr); + } + return value; +} + +void QVistaHelper::drawTitleBar(QPainter *painter) +{ + Q_ASSERT(backButton_); + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + QBackingStore *backingStore = backButton_->backingStore(); + HDC hdc = static_cast<HDC>(nativeInterface->nativeResourceForBackingStore("getDC", backingStore)); + + if (vistaState() == VistaAero) + drawBlackRect(QRect(0, 0, wizard->width(), + titleBarSize() + topOffset()), hdc); + const int btnTop = backButton_->mapToParent(QPoint()).y(); + const int btnHeight = backButton_->size().height(); + const int verticalCenter = (btnTop + btnHeight / 2) - 1; + + const QString text = wizard->window()->windowTitle(); + const QFont font = QApplication::font("QWorkspaceTitleBar"); + const QFontMetrics fontMetrics(font); + const QRect brect = fontMetrics.boundingRect(text); + int textHeight = brect.height(); + int textWidth = brect.width(); + int glowOffset = 0; + + if (vistaState() == VistaAero) { + textHeight += 2 * glowSize(); + textWidth += 2 * glowSize(); + glowOffset = glowSize(); + } + + drawTitleText( + painter, text, + QRect(titleOffset() - glowOffset, verticalCenter - textHeight / 2, textWidth, textHeight), + hdc); + + if (!wizard->windowIcon().isNull()) { + QRect rect(leftMargin(), verticalCenter - iconSize() / 2, iconSize(), iconSize()); + HICON hIcon = 0; //###FIXME wizard->windowIcon().pixmap(iconSize()).toWinHICON(); + DrawIconEx(hdc, rect.left(), rect.top(), hIcon, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT); + DestroyIcon(hIcon); + } +} + +void QVistaHelper::setTitleBarIconAndCaptionVisible(bool visible) +{ + if (is_vista) { + WIZ_WTA_OPTIONS opt; + opt.dwFlags = WIZ_WTNCA_NODRAWICON | WIZ_WTNCA_NODRAWCAPTION; + if (visible) + opt.dwMask = 0; + else + opt.dwMask = WIZ_WTNCA_NODRAWICON | WIZ_WTNCA_NODRAWCAPTION; + HWND handle = QApplicationPrivate::getHWNDForWidget(wizard); + pSetWindowThemeAttribute(handle, WIZ_WTA_NONCLIENT, &opt, sizeof(WIZ_WTA_OPTIONS)); + } +} + +bool QVistaHelper::winEvent(MSG* msg, long* result) +{ + bool retval = true; + + switch (msg->message) { + case WM_NCHITTEST: { + LRESULT lResult; + pDwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lResult); + if (lResult == HTCLOSE || lResult == HTMAXBUTTON || lResult == HTMINBUTTON || lResult == HTHELP) + *result = lResult; + else + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + break; + } + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WIZ_WM_NCMOUSELEAVE: { + LRESULT lResult; + pDwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lResult); + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + break; + } + case WM_NCCALCSIZE: { + NCCALCSIZE_PARAMS* lpncsp = (NCCALCSIZE_PARAMS*)msg->lParam; + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + lpncsp->rgrc[0].top -= (vistaState() == VistaAero ? titleBarSize() : 0); + break; + } + default: + retval = false; + } + + return retval; +} + +void QVistaHelper::setMouseCursor(QPoint pos) +{ +#ifndef QT_NO_CURSOR + if (rtTop.contains(pos)) + wizard->setCursor(Qt::SizeVerCursor); + else + wizard->setCursor(Qt::ArrowCursor); +#endif +} + +void QVistaHelper::mouseEvent(QEvent *event) +{ + switch (event->type()) { + case QEvent::MouseMove: + mouseMoveEvent(static_cast<QMouseEvent *>(event)); + break; + case QEvent::MouseButtonPress: + mousePressEvent(static_cast<QMouseEvent *>(event)); + break; + case QEvent::MouseButtonRelease: + mouseReleaseEvent(static_cast<QMouseEvent *>(event)); + break; + default: + break; + } +} + +// The following hack ensures that the titlebar is updated correctly +// when the wizard style changes to and from AeroStyle. Specifically, +// this function causes a Windows message of type WM_NCCALCSIZE to +// be triggered. +void QVistaHelper::setWindowPosHack() +{ + const int x = wizard->geometry().x(); // ignored by SWP_NOMOVE + const int y = wizard->geometry().y(); // ignored by SWP_NOMOVE + const int w = wizard->width(); + const int h = wizard->height(); + HWND handle = QApplicationPrivate::getHWNDForWidget(wizard); + SetWindowPos(handle, 0, x, y, w, h, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); +} + +// The following hack allows any QWidget subclass to access +// QWidgetPrivate::topData() without being declared as a +// friend by QWidget. +class QHackWidget : public QWidget +{ +public: + Q_DECLARE_PRIVATE(QWidget) + QTLWExtra* topData() { return d_func()->topData(); } +}; + +void QVistaHelper::collapseTopFrameStrut() +{ + QTLWExtra *top = ((QHackWidget *)wizard)->d_func()->topData(); + int x1, y1, x2, y2; + top->frameStrut.getCoords(&x1, &y1, &x2, &y2); + top->frameStrut.setCoords(x1, 0, x2, y2); +} + +bool QVistaHelper::handleWinEvent(MSG *message, long *result) +{ + if (message->message == WIZ_WM_THEMECHANGED || message->message == WIZ_WM_DWMCOMPOSITIONCHANGED) + cachedVistaState = Dirty; + + bool status = false; + if (wizard->wizardStyle() == QWizard::AeroStyle && vistaState() == VistaAero) { + status = winEvent(message, result); + if (message->message == WM_NCCALCSIZE) { + if (status) + collapseTopFrameStrut(); + } else if (message->message == WM_NCPAINT) { + wizard->update(); + } + } + return status; +} + +void QVistaHelper::resizeEvent(QResizeEvent * event) +{ + Q_UNUSED(event); + rtTop = QRect (0, 0, wizard->width(), frameSize()); + int height = captionSize() + topOffset(); + if (vistaState() == VistaBasic) + height -= titleBarSize(); + rtTitle = QRect (0, frameSize(), wizard->width(), height); +} + +void QVistaHelper::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter painter(wizard); + drawTitleBar(&painter); +} + +void QVistaHelper::mouseMoveEvent(QMouseEvent *event) +{ + if (wizard->windowState() & Qt::WindowMaximized) { + event->ignore(); + return; + } + + QRect rect = wizard->geometry(); + if (pressed) { + switch (change) { + case resizeTop: + { + const int dy = event->pos().y() - pressedPos.y(); + if ((dy > 0 && rect.height() > wizard->minimumHeight()) + || (dy < 0 && rect.height() < wizard->maximumHeight())) + rect.setTop(rect.top() + dy); + } + break; + case movePosition: { + QPoint newPos = event->pos() - pressedPos; + rect.moveLeft(rect.left() + newPos.x()); + rect.moveTop(rect.top() + newPos.y()); + break; } + default: + break; + } + wizard->setGeometry(rect); + + } else if (vistaState() == VistaAero) { + setMouseCursor(event->pos()); + } + event->ignore(); +} + +void QVistaHelper::mousePressEvent(QMouseEvent *event) +{ + change = noChange; + + if (wizard->windowState() & Qt::WindowMaximized) { + event->ignore(); + return; + } + + if (rtTitle.contains(event->pos())) { + change = movePosition; + } else if (rtTop.contains(event->pos())) + change = (vistaState() == VistaAero) ? resizeTop : movePosition; + + if (change != noChange) { + if (vistaState() == VistaAero) + setMouseCursor(event->pos()); + pressed = true; + pressedPos = event->pos(); + } else { + event->ignore(); + } +} + +void QVistaHelper::mouseReleaseEvent(QMouseEvent *event) +{ + change = noChange; + if (pressed) { + pressed = false; + wizard->releaseMouse(); + if (vistaState() == VistaAero) + setMouseCursor(event->pos()); + } + event->ignore(); +} + +bool QVistaHelper::eventFilter(QObject *obj, QEvent *event) +{ + if (obj != wizard) + return QObject::eventFilter(obj, event); + + if (event->type() == QEvent::MouseMove) { + QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + HWND handle = QApplicationPrivate::getHWNDForWidget(wizard); + msg.hwnd = handle; + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCMOUSEMOVE; + winEvent(&msg, &result); + } else if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + HWND handle = QApplicationPrivate::getHWNDForWidget(wizard); + msg.hwnd = handle; + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCLBUTTONDOWN; + winEvent(&msg, &result); + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + HWND handle = QApplicationPrivate::getHWNDForWidget(wizard); + msg.hwnd = handle; + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCLBUTTONUP; + winEvent(&msg, &result); + } + + return false; +} + +HFONT QVistaHelper::getCaptionFont(HANDLE hTheme) +{ + LOGFONT lf = {0}; + + if (!hTheme) + pGetThemeSysFont(hTheme, WIZ_TMT_CAPTIONFONT, &lf); + else + { + NONCLIENTMETRICS ncm = {sizeof(NONCLIENTMETRICS)}; + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, false); + lf = ncm.lfMessageFont; + } + return CreateFontIndirect(&lf); +} + +bool QVistaHelper::drawTitleText(QPainter *painter, const QString &text, const QRect &rect, HDC hdc) +{ + bool value = false; + if (vistaState() == VistaAero) { + HWND handle = QApplicationPrivate::getHWNDForWidget(QApplication::desktop()); + HANDLE hTheme = pOpenThemeData(handle, L"WINDOW"); + if (!hTheme) return false; + // Set up a memory DC and bitmap that we'll draw into + HDC dcMem; + HBITMAP bmp; + BITMAPINFO dib = {{0}}; + dcMem = CreateCompatibleDC(hdc); + + dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib.bmiHeader.biWidth = rect.width(); + dib.bmiHeader.biHeight = -rect.height(); + dib.bmiHeader.biPlanes = 1; + dib.bmiHeader.biBitCount = 32; + dib.bmiHeader.biCompression = BI_RGB; + + bmp = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); + + // Set up the DC + HFONT hCaptionFont = getCaptionFont(hTheme); + HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); + HFONT hOldFont = (HFONT)SelectObject(dcMem, (HGDIOBJ) hCaptionFont); + + // Draw the text! + WIZ_DTTOPTS dto = { sizeof(WIZ_DTTOPTS) }; + const UINT uFormat = WIZ_DT_SINGLELINE|WIZ_DT_CENTER|WIZ_DT_VCENTER|WIZ_DT_NOPREFIX; + RECT rctext ={0,0, rect.width(), rect.height()}; + + dto.dwFlags = WIZ_DTT_COMPOSITED|WIZ_DTT_GLOWSIZE; + dto.iGlowSize = glowSize(); + + pDrawThemeTextEx(hTheme, dcMem, 0, 0, (LPCWSTR)text.utf16(), -1, uFormat, &rctext, &dto ); + BitBlt(hdc, rect.left(), rect.top(), rect.width(), rect.height(), dcMem, 0, 0, SRCCOPY); + SelectObject(dcMem, (HGDIOBJ) hOldBmp); + SelectObject(dcMem, (HGDIOBJ) hOldFont); + DeleteObject(bmp); + DeleteObject(hCaptionFont); + DeleteDC(dcMem); + //ReleaseDC(hwnd, hdc); + } else if (vistaState() == VistaBasic) { + painter->drawText(rect, text); + } + return value; +} + +bool QVistaHelper::drawBlackRect(const QRect &rect, HDC hdc) +{ + bool value = false; + if (vistaState() == VistaAero) { + // Set up a memory DC and bitmap that we'll draw into + HDC dcMem; + HBITMAP bmp; + BITMAPINFO dib = {{0}}; + dcMem = CreateCompatibleDC(hdc); + + dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib.bmiHeader.biWidth = rect.width(); + dib.bmiHeader.biHeight = -rect.height(); + dib.bmiHeader.biPlanes = 1; + dib.bmiHeader.biBitCount = 32; + dib.bmiHeader.biCompression = BI_RGB; + + bmp = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); + HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); + + BitBlt(hdc, rect.left(), rect.top(), rect.width(), rect.height(), dcMem, 0, 0, SRCCOPY); + SelectObject(dcMem, (HGDIOBJ) hOldBmp); + + DeleteObject(bmp); + DeleteDC(dcMem); + } + return value; +} + +bool QVistaHelper::resolveSymbols() +{ + static bool tried = false; + if (!tried) { + tried = true; + QSystemLibrary dwmLib(L"dwmapi"); + pDwmIsCompositionEnabled = + (PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled"); + if (pDwmIsCompositionEnabled) { + pDwmDefWindowProc = (PtrDwmDefWindowProc)dwmLib.resolve("DwmDefWindowProc"); + pDwmExtendFrameIntoClientArea = + (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea"); + } + QSystemLibrary themeLib(L"uxtheme"); + pIsAppThemed = (PtrIsAppThemed)themeLib.resolve("IsAppThemed"); + if (pIsAppThemed) { + pDrawThemeBackground = (PtrDrawThemeBackground)themeLib.resolve("DrawThemeBackground"); + pGetThemePartSize = (PtrGetThemePartSize)themeLib.resolve("GetThemePartSize"); + pGetThemeColor = (PtrGetThemeColor)themeLib.resolve("GetThemeColor"); + pIsThemeActive = (PtrIsThemeActive)themeLib.resolve("IsThemeActive"); + pOpenThemeData = (PtrOpenThemeData)themeLib.resolve("OpenThemeData"); + pCloseThemeData = (PtrCloseThemeData)themeLib.resolve("CloseThemeData"); + pGetThemeSysFont = (PtrGetThemeSysFont)themeLib.resolve("GetThemeSysFont"); + pDrawThemeTextEx = (PtrDrawThemeTextEx)themeLib.resolve("DrawThemeTextEx"); + pSetWindowThemeAttribute = (PtrSetWindowThemeAttribute)themeLib.resolve("SetWindowThemeAttribute"); + } + } + + return ( + pDwmIsCompositionEnabled != 0 + && pDwmDefWindowProc != 0 + && pDwmExtendFrameIntoClientArea != 0 + && pIsAppThemed != 0 + && pDrawThemeBackground != 0 + && pGetThemePartSize != 0 + && pGetThemeColor != 0 + && pIsThemeActive != 0 + && pOpenThemeData != 0 + && pCloseThemeData != 0 + && pGetThemeSysFont != 0 + && pDrawThemeTextEx != 0 + && pSetWindowThemeAttribute != 0 + ); +} + +int QVistaHelper::titleOffset() +{ + int iconOffset = wizard ->windowIcon().isNull() ? 0 : iconSize() + textSpacing; + return leftMargin() + iconOffset; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSVISTA + +#endif // QT_NO_WIZARD diff --git a/src/widgets/dialogs/qwizard_win_p.h b/src/widgets/dialogs/qwizard_win_p.h new file mode 100644 index 0000000000..f53a9ba75c --- /dev/null +++ b/src/widgets/dialogs/qwizard_win_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIZARD_WIN_P_H +#define QWIZARD_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_WIZARD +#ifndef QT_NO_STYLE_WINDOWSVISTA + +#include <qt_windows.h> +#include <qobject.h> +#include <qwidget.h> +#include <qabstractbutton.h> +#include <QtWidgets/private/qwidget_p.h> +#include <QtWidgets/private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +class QVistaBackButton : public QAbstractButton +{ +public: + QVistaBackButton(QWidget *widget); + + QSize sizeHint() const; + inline QSize minimumSizeHint() const + { return sizeHint(); } + + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); +}; + +class QWizard; + +class QVistaHelper : public QObject +{ +public: + QVistaHelper(QWizard *wizard); + ~QVistaHelper(); + enum TitleBarChangeType { NormalTitleBar, ExtendedTitleBar }; + bool setDWMTitleBar(TitleBarChangeType type); + void setTitleBarIconAndCaptionVisible(bool visible); + void mouseEvent(QEvent *event); + bool handleWinEvent(MSG *message, long *result); + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); + QVistaBackButton *backButton() const { return backButton_; } + void disconnectBackButton() { if (backButton_) backButton_->disconnect(); } + void hideBackButton() { if (backButton_) backButton_->hide(); } + void setWindowPosHack(); + QColor basicWindowFrameColor(); + enum VistaState { VistaAero, VistaBasic, Classic, Dirty }; + static VistaState vistaState(); + static int titleBarSize() { return frameSize() + captionSize(); } + static int topPadding() { // padding under text + return int(QStyleHelper::dpiScaled( + QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? 4 : 6)); + } + static int topOffset() { + static int aeroOffset = QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? + QStyleHelper::dpiScaled(4) : QStyleHelper::dpiScaled(13); + return (titleBarSize() + (vistaState() == VistaAero ? aeroOffset : 3)); } +private: + static HFONT getCaptionFont(HANDLE hTheme); + bool drawTitleText(QPainter *painter, const QString &text, const QRect &rect, HDC hdc); + static bool drawBlackRect(const QRect &rect, HDC hdc); + + static int frameSize() { return GetSystemMetrics(SM_CYSIZEFRAME); } + static int captionSize() { return GetSystemMetrics(SM_CYCAPTION); } + + static int backButtonSize() { return int(QStyleHelper::dpiScaled(30)); } + static int iconSize() { return 16; } // Standard Aero + static int glowSize() { return 10; } + int leftMargin() { return backButton_->isVisible() ? backButtonSize() + iconSpacing : 0; } + + int titleOffset(); + bool resolveSymbols(); + void drawTitleBar(QPainter *painter); + void setMouseCursor(QPoint pos); + void collapseTopFrameStrut(); + bool winEvent(MSG *message, long *result); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + + static bool is_vista; + static VistaState cachedVistaState; + static bool isCompositionEnabled(); + static bool isThemeActive(); + enum Changes { resizeTop, movePosition, noChange } change; + QPoint pressedPos; + bool pressed; + QRect rtTop; + QRect rtTitle; + QWizard *wizard; + QVistaBackButton *backButton_; + + int titleBarOffset; // Extra spacing above the text + int iconSpacing; // Space between button and icon + int textSpacing; // Space between icon and text +}; + + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSVISTA +#endif // QT_NO_WIZARD +#endif // QWIZARD_WIN_P_H |